Shady - Robot Personal Assistant

by InventorKrish in Circuits > Arduino

16 Views, 0 Favorites, 0 Comments

Shady - Robot Personal Assistant

Screenshot 2025-05-03 153643.png

Hey, I’m Krishavardan, and I’m 12 years old. Meet Shady — a cutting-edge, AI-powered robot designed to be your personal assistant. Shady can handle everything from answering time-based questions to providing real-time updates. But what makes Shady stand out? It follows you around. Whether you need the current time, weather updates, or assistance with staying organized, Shady is always by your side. Powered by a combination of advanced AI and smart hardware, this robot is more than just intelligent — it’s responsive, adaptive, and perfectly aligned with your everyday needs.

You can check out the Shady - Personal Assistant Demo and get updates on the robot on my YouTube channel here:

🔗 https://www.youtube.com/@InventorKrish

Supplies

For Shady, I used 2x Arduino Uno R3 boards to control the various components and bring the robot to life. The robot's movement is powered by 2x DC motors, which are driven by an L298N Motor Driver. To power the system, I used a 1x 11.1V Lithium Battery along with a Current Regulator to ensure the correct voltage is supplied. The robot features a color tracking camera to enhance its interactive capabilities, and everything is wired together using a breadboard and jumper wires. For physical assembly, I used popsicle sticks, cardboard, glue, and hot glue to create the robot's chassis. Additionally, I used a USB cable for programming, and all of this is controlled and programmed through a laptop.

Creating the 3D Model

Screenshot 2025-05-03 140733.png
Screenshot 2025-05-03 140703.png
Screenshot 2025-05-03 140630.png

The 3D model accurately represents the chassis and overall appearance of the robot.

Creating the Chassis

Screenshot 2025-05-03 135932.png
Screenshot 2025-05-03 140010.png
Screenshot 2025-05-03 140034.png
Screenshot 2025-05-03 140047.png

The chassis is built from popsicle sticks and cardboard, held together using a combination of hot glue and Elmer's glue for added strength and stability.

Creating the Circuit Diagram

Screenshot 2025-05-03 141805.png
Screenshot 2025-05-03 142523.png

The images provided represent an early attempt to visualize the circuit design. The first circuit, which is housed inside the robot, follows this configuration: the receiver is connected to an Arduino Uno R3, which then links to an L298N dual motor driver. This driver is connected to a current regulator, which powers two DC motors. Additional components supporting this setup include an 11.1V battery for power, a breadboard for circuit organization, and jumper wires for all necessary connections. The second circuit, which connects externally to a laptop, is much simpler. It consists of a direct connection from the laptop to an Arduino Uno R3, which is then connected to a transmitter. No additional hardware is used in this secondary circuit.

Creating the Circuit

Screenshot 2025-05-03 153643.png
Screenshot 2025-05-03 153951.png
Screenshot 2025-05-03 153934.png

The image shows the physical circuit setup. Some components might be missing due to last-minute modifications, and part of the wiring is concealed inside the robot, routed through an opening underneath where the wheels are mounted.

AI Features

Screenshot 2025-05-03 160850.png
Screenshot 2025-05-03 160835.png

Shady is an advanced AI-powered personal assistant designed to deliver fast, intelligent, and context-aware interactions. Her core features include real-time updates on the date, year, month, week, day, and time, along with the ability to recognize and respond to specific events. She provides live weather forecasts, delivers news updates, and retrieves detailed information from Wikipedia. In addition to being informative, Shady functions as a productivity companion, offering reminder alerts to keep you on schedule. On the system control side, she’s equipped with commands to shut down, restart, or lock the computer, giving her a powerful edge as both an assistant and a digital overseer.

AI's Code

The source code for the AI integrated into the robot governs its time-sensitive operations, real-time data processing, and decision-making functions.

import speech_recognition as sr

import pyttsx3

import datetime

import wikipedia

import warnings

from bs4 import BeautifulSoup

import webbrowser

import os

import time

import subprocess

import wolframalpha

import json

import requests

import random


# Suppress the GuessedAtParserWarning from wikipedia package by specifying parser explicitly

import wikipedia.wikipedia as wp_wikipedia

def custom_parse(html):

return BeautifulSoup(html, features="html.parser")

wp_wikipedia.BeautifulSoup = custom_parse

warnings.filterwarnings("ignore", category=UserWarning, module='wikipedia')


# Shady Boot Up

print('I am now online and ready to assist you.')


engine = pyttsx3.init()

voices = engine.getProperty('voices')


# Use Microsoft Zira if available

zira_voice_id = None

for voice in voices:

if "Zira" in voice.name:

zira_voice_id = voice.id

break

if zira_voice_id:

engine.setProperty('voice', zira_voice_id)

else:

engine.setProperty('voice', voices[0].id) # fallback


def speak(text):

print(text)

engine.say(text)

engine.runAndWait()


def get_weather(city="Frisco"):

api_key = "8ef61edcf1c576d65d836254e11ea420" # This should be secured in production environments.

base_url = "https://api.openweathermap.org/data/2.5/weather?"

complete_url = base_url + "appid=" + api_key + "&q=" + city

try:

response = requests.get(complete_url)

x = response.json()

if x["cod"] != "404":

y = x["main"]

current_temperature = y["temp"]

current_humidity = y["humidity"]

z = x["weather"]

weather_description = z[0]["description"]

temp_celsius = current_temperature - 273.15

weather_info = (

f"Currently in {city.title()}: {weather_description}, "

f"{temp_celsius:.1f} degrees Celsius, humidity at {current_humidity}%."

)

return weather_info

else:

return f"City {city} could not be found."

except Exception:

return "Unable to retrieve weather information at the moment."


def get_news_flash():

placeholders = [

"Breaking news: Coffee demand is rising globally.",

"Update: Artificial Intelligence continues to evolve rapidly.",

"News: Global technological innovation remains at an all-time high.",

"Report: Proactive learning and development observed worldwide.",

]

return random.choice(placeholders)


def get_events():

events = [

"Meeting up with your friends and improving Shady."

]

if events:

return "Your schedule includes: " + "; ".join(events)

else:

return "You have no upcoming events."


def smart_answer(statement):

try:

result = wikipedia.summary(statement, sentences=2)

return result

except Exception:

return "Information on that topic could not be retrieved."


def take_command():

r = sr.Recognizer()

with sr.Microphone() as source:

print("Listening...")

audio = r.listen(source)

try:

statement = r.recognize_google(audio, language='en-in')

print(f"You said: {statement}\n")

except Exception:

return "none"

return statement.lower()


reminder_set = False

reminder_text = ""

reminder_start_time = None

next_reminder_interval = None


def set_reminder(text):

global reminder_set, reminder_text, reminder_start_time, next_reminder_interval

reminder_text = text

reminder_set = True

reminder_start_time = time.time()

next_reminder_interval = None

speak(f"Reminder set: {reminder_text} I will remind you after one hour to three hours.")


def run_assistant():

speak("I am fully operational and standing by.")


try:

while True:

global reminder_set, reminder_text, reminder_start_time, next_reminder_interval

# Check for reminder timing

if reminder_set:

current_time = time.time()

elapsed_time = current_time - reminder_start_time

# Start reminders only after 5 seconds (for testing)

if elapsed_time >= 5:

if next_reminder_interval is None:

# Set next reminder interval randomly between 5 to 15 seconds (for testing)

next_reminder_interval = random.randint(5, 15)

reminder_start_time = current_time # reset start time for next interval

elif elapsed_time >= next_reminder_interval:

speak(f"Reminder: {reminder_text}")

# After first quick reminder, reset to 1 to 3 hours interval

reminder_start_time = current_time

next_reminder_interval = random.randint(3600, 10800)

# Clear reminder so it does not repeat the same reminder

reminder_set = False

reminder_text = ""


statement = take_command()

if statement == "none" or not statement.strip():

continue


if "goodbye" in statement or "stop" in statement or "shutdown" in statement:

speak("I am shutting down now. Have a productive day.")

print("I am shutting down now. Goodbye.")

break


elif "time" in statement:

str_time = datetime.datetime.now().strftime("%I:%M %p").lstrip("0")

speak(f"The current time is {str_time}.")


elif "date" in statement:

str_date = datetime.datetime.now().strftime("%B %d, %Y")

speak(f"Today's date is {str_date}.")


elif "year" in statement:

year = datetime.datetime.now().year

speak(f"The current year is {year}.")


elif "month" in statement:

month = datetime.datetime.now().strftime("%B")

speak(f"The current month is {month}.")


elif "week" in statement or "weekday" in statement:

week_number = datetime.datetime.now().isocalendar()[1]

speak(f"This is week number {week_number} of the year.")


elif "day" in statement:

day = datetime.datetime.now().strftime("%d").lstrip("0")

speak(f"Today is the {day}th day of the month.")


elif "weather" in statement:

city = "Frisco" # Default city

words = statement.split() # Splitting the statement into words to look for a city name


# Search for a city name in the command

for i, word in enumerate(words):

if word.lower() == "weather" and i + 1 < len(words):

city = words[i + 1] # Set the city name after "weather"

break


# Fetch weather information for the city

weather_info = get_weather(city)

speak(weather_info)


# Extract temperature from the weather_info to give clothing recommendation

temp_celsius = float(weather_info.split()[5]) # Extract the temperature (assuming temp is in 6th position)


# Clothing recommendations based on the temperature

if temp_celsius < 10:

speak("Wear a warm jacket and scarf.")

elif temp_celsius < 20:

speak("A light jacket or sweater should be fine.")

else:

speak("Dress lightly! T-shirt and pants are perfect.")


elif "news" in statement:

news = get_news_flash()

speak(news)


elif "event" in statement or "schedule" in statement:

events = get_events()

speak(events)


elif "search" in statement:

search_query = statement.replace('search', '').strip()

if search_query:

webbrowser.open(f"https://www.google.com/search?q={search_query}")

speak(f"Opening Google search for {search_query}.")


elif "lock" in statement:

os.system('rundll32.exe user32.dll,LockWorkStation')

speak("System is now locked.")


elif "restart" in statement:

os.system('shutdown /r /f')

speak("System is restarting now.")


elif "shut down" in statement:

os.system('shutdown /s /f')

speak("System is shutting down now.")


elif "reminder" in statement:

# Remove the keyword "reminder" from the statement to get the reminder text

reminder_text = statement.replace("reminder", "").strip()

if reminder_text:

set_reminder(reminder_text)

speak("Reminder Set.")

else:

speak("Please specify what you want me to remind you about.")


else:

speak("Did you say something? Would you like me to provide information on it?")

user_response = take_command()

if user_response in ["yes", "yeah", "sure", "go ahead"]:

answer = smart_answer(statement)

if answer == "Information on that topic could not be retrieved.":

speak("Sorry, I couldn't find any information on that topic.")

else:

speak(answer)

elif user_response in ["no", "nope", "not really", "no thanks"]:

speak("Okay, I'll be waiting for your command.")

else:

speak("I didn't understand that. Please respond with 'yes' or 'no'.")


except Exception as e:

print(f"Error: {e}")

speak("An unexpected error occurred. Attempting to recover.")


if __name__ == '__main__':

run_assistant()


Robot's Calculation/movement Code

The provided code controls the robot's movement and calculates its direction and distance based on the circumference of its wheels, which is 1 inch. The motors are connected to an L298N motor driver, which is controlled by an Arduino Uno R3. The Arduino receives commands from a transmitter connected to another Arduino, allowing the robot to move in specific directions (forward, backward, left, or right). The code calculates how many rotations of the wheel are needed to cover a certain distance, ensuring the robot moves the desired amount. The Arduino continuously adjusts the movement by sending commands to the motor driver, while also determining the robot's direction, allowing it to follow the intended path accurately. Source Code:

import cv2

import numpy as np

import serial

import time


# Set up serial communication with the Arduino

arduino = serial.Serial('COM3', 9600) # Update with your Arduino's COM port

time.sleep(2) # Wait for Arduino to initialize


# Camera setup

cap = cv2.VideoCapture(0) # Use your camera (0 for default webcam)


# Color range for detecting the object (example: detecting a red object)

lower_red = np.array([0, 120, 70])

upper_red = np.array([10, 255, 255])


# Wheel specifications

wheel_circumference = 1.0 # Wheel circumference in inches

steps_per_rotation = 360 # Number of steps for one full rotation of the wheel


# Variables for distance and previous position

prev_position = None

prev_distance = 0 # To keep track of total distance traveled


def send_command(command):

""" Send a command to the Arduino over serial communication. """

arduino.write(command.encode())


def detect_object(frame):

""" Detect a colored object in the frame (red in this case). """

hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)

mask = cv2.inRange(hsv, lower_red, upper_red)


# Find contours of the object

contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

if contours:

# Get the largest contour (assuming the object is the largest one)

largest_contour = max(contours, key=cv2.contourArea)

# Get the center of the object

M = cv2.moments(largest_contour)

if M["m00"] != 0:

cx = int(M["m10"] / M["m00"])

cy = int(M["m01"] / M["m00"])

return (cx, cy)

return None


def calculate_rotations(distance):

""" Calculate the number of steps needed for the motors to travel a certain distance. """

rotations = (distance / wheel_circumference) * steps_per_rotation

return int(rotations)


def calculate_direction(prev_position, current_position):

""" Calculate the direction of movement based on object displacement. """

if prev_position is None or current_position is None:

return "STAY", 0

delta_x = current_position[0] - prev_position[0]

delta_y = current_position[1] - prev_position[1]

# Define thresholds to determine the movement direction

if abs(delta_x) > abs(delta_y):

if delta_x > 0:

return "RIGHT", abs(delta_x)

else:

return "LEFT", abs(delta_x)

else:

if delta_y > 0:

return "FORWARD", abs(delta_y)

else:

return "BACKWARD", abs(delta_y)

return "STAY", 0


def move_robot(direction, distance):

""" Move the robot based on the detected direction and distance. """

rotations = calculate_rotations(distance)

for _ in range(rotations):

send_command(direction)

time.sleep(0.1) # Adjust the delay to match the motor's speed


# Main loop to capture video and detect movement

while True:

ret, frame = cap.read()

if not ret:

break


# Detect object

current_position = detect_object(frame)

if current_position:

# Draw a circle at the object’s position

cv2.circle(frame, current_position, 10, (0, 255, 0), -1)

# Calculate direction and movement

if current_position and prev_position:

direction, displacement = calculate_direction(prev_position, current_position)

if direction != "STAY":

print(f"Direction: {direction}, Displacement: {displacement}")

# Move robot based on direction and displacement

move_robot(direction, displacement)

# Update the previous position

prev_position = current_position

# Show the frame

cv2.imshow("Robot Movement", frame)

# Exit condition (press 'q' to exit)

if cv2.waitKey(1) & 0xFF == ord('q'):

break


# Release resources

cap.release()

cv2.destroyAllWindows()