Naomi Pentrel
Published © CC BY

Guardian Robot to Track Humans and Dogs

Print, Paint, and Program a Guardian to Track Humans and Dogs Using a Pi, Camera, and Servo

IntermediateFull instructions provided2 days336

Things used in this project

Hardware components

Raspberry Pi 4 Model B
Raspberry Pi 4 Model B
+ power cable
×1
Camera Module
Raspberry Pi Camera Module
+ 50cm ribbon cable: The default 15cm ribbon cable is not long enough.
×1
SG90 Micro-servo motor
SG90 Micro-servo motor
a 180 degree SG90 servo: Because of the camera ribbon, I restricted the servo to only 180 degrees.
×1
10mm RGB LEDs with common cathode
×3
Machine Screw, M2
Machine Screw, M2
×4
Jumper wires (generic)
Jumper wires (generic)
×1
speaker
Optional if you want music. I used a 4Ω 2W speaker with connected aux in. You can use any speaker you can connect to your Pi.
×1
wire
To allow you to position the legs better, you can thread wire through them.
×1
a base for the guardian
I used a wooden disk with a hole cut in the middle and a box with a hole in the top underneath.
×1
modeling grass, stones, glue
×1
primer & acrylic paint
I ordered armour modelling paint but found that mixing my own colors from a regular acrylic paint set worked best for me.
×1
shine-through filament
To make the guardian’s lights shine through its body, use filament that allows the light to shine through and paint the parts that shouldn’t allow light to shine through.
×1

Software apps and online services

Viam Platform
Viam Robotics Viam Platform

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)
small screw drivers

Story

Read more

Custom parts and enclosures

Guardian Head with Mounts for Camera

This is a model my friend made (and gave me permission to post)

Guardian Robot, Hackable

This was an existing model we used

Schematics

Servo Wiring Diagram

Wiring diagram for the servo

Code

Full Robot Code

Python
Code that makes a guardian idle and when it detects humans or dogs or cats turn red, start music, and focus on the detected being
import asyncio
import random
import vlc

from viam.robot.client import RobotClient
from viam.rpc.dial import Credentials, DialOptions
from viam.components.board import Board
from viam.components.camera import Camera
from viam.components.servo import Servo
from viam.services.vision import VisionClient

LIVING_OBJECTS = ["Person", "Dog", "Cat", "Teddy bear"]


async def connect():
    creds = Credentials(
        type='robot-location-secret',
        payload='SECRET_FROM_VIAM_APP')
    opts = RobotClient.Options(
        refresh_interval=0,
        dial_options=DialOptions(credentials=creds)
    )
    return await RobotClient.at_address('guardian-main.vw3iu72d8n.viam.cloud', opts)


async def check_for_living_creatures(detections):
    for d in detections:
        if d.confidence > 0.6 and d.class_name in LIVING_OBJECTS:
            print("detected")
            return d


async def focus_on_creature(creature, width, servo):
    creature_midpoint = (creature.x_max + creature.x_min)/2
    image_midpoint = width/2
    center_min = image_midpoint - 0.2*image_midpoint
    center_max = image_midpoint + 0.2*image_midpoint

    movement = (image_midpoint - creature_midpoint)/image_midpoint
    angular_scale = 20
    print("MOVE BY: ")
    print(int(angular_scale*movement))

    servo_angle = await servo.get_position()
    if (creature_midpoint < center_min or creature_midpoint > center_max):
        servo_angle = servo_angle + int(angular_scale*movement)
        if servo_angle > 180:
            servo_angle = 180
        if servo_angle < 0:
            servo_angle = 0

        if servo_angle >= 0 and servo_angle <= 180:
            await servo.move(servo_angle)

    servo_return_value = await servo.get_position()
    print(f"servo get_position return value: {servo_return_value}")


class LedGroup:
    def __init__(self, group):
        print("group")
        self.group = group

    async def led_state(self, on):
        for pin in self.group:
            await pin.set(on)


async def idle_and_check_for_living_creatures(cam, detector, servo, blue_leds, red_leds, music_player):
    living_creature = None
    while True:
        random_number_checks = random.randint(0, 5)
        if music_player.is_playing():
            random_number_checks = 15
        for i in range(random_number_checks):
            img = await cam.get_image()
            detections = await detector.get_detections(img)
            living_creature = await check_for_living_creatures(detections)
            if living_creature:
                await red_leds.led_state(True)
                await blue_leds.led_state(False)
                if not music_player.is_playing():
                    music_player.play()
                return living_creature
        print("START IDLE")
        await blue_leds.led_state(True)
        await red_leds.led_state(False)
        if music_player.is_playing():
            music_player.stop()
        await servo.move(random.randint(0, 180))


async def main():
    robot = await connect()
    local = Board.from_robot(robot, 'local')
    cam = Camera.from_robot(robot, "cam")
    img = await cam.get_image()
    servo = Servo.from_robot(robot, "servo")
    red_leds = LedGroup([
        await local.gpio_pin_by_name('22'),
        await local.gpio_pin_by_name('24'),
        await local.gpio_pin_by_name('26')
    ])
    blue_leds = LedGroup([
        await local.gpio_pin_by_name('11'),
        await local.gpio_pin_by_name('13'),
        await local.gpio_pin_by_name('15')
    ])

    await blue_leds.led_state(True)

    music_player = vlc.MediaPlayer("guardian.mp3")

    # grab Viam's vision service for the detector 
    detector = VisionClient.from_robot(robot, "detector")
    while True:
        # move head periodically left and right until movement is spotted.
        living_creature = await idle_and_check_for_living_creatures(cam, detector, servo, blue_leds, red_leds, music_player)
        await focus_on_creature(living_creature, img.width, servo)
    # Don't forget to close the robot when you're done!
    await robot.close()

if __name__ == '__main__':
    asyncio.run(main())

Credits

Naomi Pentrel
1 project • 2 followers
Developer content professional, occasional blogger, crocheter, and hacker. Previously engineer at Google, Microsoft, MongoDB, and Twilio.
Contact
Thanks to Hugh Rawlinson and steveut.

Comments

Please log in or sign up to comment.