How to: HA controlled DIY pet feeder with camera and laser

Prior to wasting time with Home Assistant one of my side projects was to built a smart feeder designed to split the food into two bowls. You’ve got two cat’s you need two bowls. The feeder I built comes with a Web interface, a camera mounted on a gimbal to see who’s eating and laser pointer mounted to the camera for play time.

There are multiple post on the internet about building a feeder, however I never came across one that incorporated a camera. The camera is great as it allows my wife to verify her cats are still alive when we’re traveling. This is a picture of the first feeder I built:

Size wise it’s larger than you’d get with a store bought device, but the height was required to get a good camera view.

The Rpi_Cam_Web_Interface project provided a great starting point for the feeder as it gives you camera integration with a web server for network based access. It didn’t have the pan and tilt feature so I added a frame with buttons to control the camera position. I wrote a python script to interface with the feeder motor and servos while providing network access for control. The Rpi_Cam_Web_Interface project provided the ability to add a few buttons, which I used to enable manual feeding and play interaction.

Here we have a view of the web interface:

While traveling I can access the setup via a VPN. Which is OK but I needed and easier integration for my wife. With my adventures with home assistant I realized once I added Nabu Casa I could make the feeder directly available to my wife via the Home Assistant interface. The network based interface I built for the feeder made for a easy HA integration.

This is what the interface looks like in Home assistant.

Fjramirez1987 has a PTZ camera card setup here that I used for the starting point. I added 4 additional icons for addional feeder functionality:

  • apple icon for the small food drop
  • burger and drink icon for the large food drop
  • teeter totter to kick off laser play mode
  • a light bulb to turn on the light in the room for better viewing at night

The feeder was already using the crontab to schedule automatic food drops. I could have used the home assistant scheduler as an alternative to using the crontab but I figured it was best to have the auto feeding continue to work standalone. So I used a stacked horizontal and vertical cards populated with this time picker card and entity cards to create an interface to update the crontab on the feeder. This gives the following interface.

The schedule creation takes up a lot of space so if you have a suggestion on some cards that would make this more compact please let me know.

In home assistant I needed a few variables and shell_commands to make this thing work. The following was included in my configuration.yam:

input_datetime:
  feed1:
    has_date: false
    has_time: true
  feed2:
    has_date: false
    has_time: true
  feed3:
    has_date: false
    has_time: true
  feed4:
    has_date: false
    has_time: true

input_select:
  feed_size_1:
    options:
      - "0"
      - "s"
      - "D"
  feed_size_2:
    options:
      - "0"
      - "s"
      - "D"
  feed_size_3:
    options:
      - "0"
      - "s"
      - "D"
  feed_size_4:
    options:
      - "0"
      - "s"
      - "D"

input_boolean:
  update_feeder_schedule:
    initial: off

shell_command:
  feeder_ctl: '/config/shell_cmds/feeder_ctl {{ host }} {{ direction }}'
  update_feeder_schedule: '/config/shell_cmds/update_feeder_schedule {{ l1 }} {{ l2 }} {{ l3 }} {{ l4 }}'

Here’s the content for the two shell scripts:

#!/bin/bash
# feeder_ctl
nc -w 0 $1 33333  <<< $2
#!/bin/bash
# update_feeder_schedule
ssh -i /config/.ssh/id_rsa -o StrictHostKeyChecking=no [email protected] ./update_feeder_schedule "$1,$2,$3,$4"

The update_feeder_schedule script is using a shared key to login to the feeder raspberry pi. So you need to create an rsa key pair and place them in a .ssh directory under your main homeassistant directory.

So that the shell scripts can be accesses I added these lines to the configuration.yaml file:

homeassistant:
  allowlist_external_dirs:
    - '/config/shell_cmds/'

Here’s the camera view card:

type: picture-elements
camera_view: live
camera_image: camera.feeder
elements:
  - type: icon
    icon: mdi:arrow-left-drop-circle
    tap_action:
      action: call-service
      service: shell_command.feeder_ctl
      service_data:
        host: 192.168.10.10
        direction: l
    style:
      bottom: 45%
      left: 5%
      color: white
      opacity: 0.5
      transform: scale(1.5, 1.5)
  - type: icon
    icon: mdi:arrow-right-drop-circle
    tap_action:
      action: call-service
      service: shell_command.feeder_ctl
      service_data:
        host: 192.168.10.10
        direction: r
    style:
      bottom: 45%
      right: 5%
      color: white
      opacity: 0.5
      transform: scale(1.5, 1.5)
  - type: icon
    icon: mdi:arrow-up-drop-circle
    tap_action:
      action: call-service
      service: shell_command.feeder_ctl
      service_data:
        host: 192.168.10.10
        direction: u
    style:
      top: 10%
      left: 46%
      color: white
      opacity: 0.5
      transform: scale(1.5, 1.5)
  - type: icon
    icon: mdi:arrow-down-drop-circle
    tap_action:
      action: call-service
      service: shell_command.feeder_ctl
      service_data:
        host: 192.168.10.10
        direction: d
    style:
      bottom: 10%
      left: 46%
      color: white
      opacity: 0.5
      transform: scale(1.5, 1.5)
  - type: icon
    icon: mdi:food-apple
    tap_action:
      action: call-service
      service: shell_command.feeder_ctl
      service_data:
        host: 192.168.10.10
        direction: u
    style:
      bottom: 10%
      left: 2.5%
      color: white
      opacity: 0.5
      transform: scale(1.5, 1.5)
  - type: icon
    icon: mdi:food
    tap_action:
      action: call-service
      service: shell_command.feeder_ctl
      service_data:
        host: 192.168.10.10
        direction: d
    style:
      bottom: 10%
      left: 13%
      color: white
      opacity: 0.5
      transform: scale(1.5, 1.5)
  - type: icon
    icon: mdi:seesaw
    tap_action:
      action: call-service
      service: shell_command.feeder_ctl
      service_data:
        host: 192.168.10.10
        direction: u
    style:
      bottom: 10%
      left: 80%
      color: white
      opacity: 0.5
      transform: scale(1.5, 1.5)
  - type: icon
    icon: mdi:arrow-expand-all
    tap_action:
      action: more-info
    entity: camera.feeder
    style:
      top: 5%
      right: 5%
      color: white
      opacity: 0.5
      transform: scale(1.5, 1.5)
  - entity: switch.master_bath
    image: /local/light-on.png
    state_filter:
      'on': brightness(130%) saturate(1.5)
      'off': brightness(50%) saturate(.5)
    state_image:
      'on': /local/light-on.png
    style:
      left: 93%
      bottom: 3%
    tap_action:
      action: toggle
    type: image

Here’s the schedule configuration card

type: vertical-stack
cards:
  - type: horizontal-stack
    cards:
      - type: custom:time-picker-card
        entity: input_datetime.feed1
        hour_mode: 24
        hour_step: 1
        minute_step: 5
        second_step: 5
        name: ''
        layout:
          name: header
          align_controls: center
      - type: entity
        entity: input_select.feed_size_1
  - type: horizontal-stack
    cards:
      - type: custom:time-picker-card
        entity: input_datetime.feed2
        hour_mode: 24
        hour_step: 1
        minute_step: 5
        second_step: 5
        name: ''
        layout:
          name: header
          align_controls: center
      - type: entity
        entity: input_select.feed_size_2
  - type: horizontal-stack
    cards:
      - type: custom:time-picker-card
        entity: input_datetime.feed3
        hour_mode: 24
        hour_step: 1
        minute_step: 5
        second_step: 5
        name: ''
        layout:
          name: header
          align_controls: center
      - type: entity
        entity: input_select.feed_size_3
  - type: horizontal-stack
    cards:
      - type: custom:time-picker-card
        entity: input_datetime.feed4
        hour_mode: 24
        hour_step: 1
        minute_step: 5
        second_step: 5
        name: ''
        layout:
          name: header
          align_controls: center
      - type: entity
        entity: input_select.feed_size_4
  - type: horizontal-stack
    cards:
      - type: button
        tap_action:
          action: toggle
        name: Send Schedule
        icon_height: 20px
        icon: mdi:send
        entity: input_boolean.update_feeder_schedule
        show_state: true

I added the following to the automation.yaml file to kick send the feeder schedule over to the feeder raspberry pi:

- id: '1643509662749'
  alias: update_feeder_schedule
  description: ''
  trigger:
  - platform: state
    entity_id: input_boolean.update_feeder_schedule
    to: 'on'
  condition: []
  action:
  - service: shell_command.update_feeder_schedule
    data_template:
      l1: "{{ states('input_datetime.feed1') }},{{ states('input_select.feed_size_1') }}"
      l2: "{{ states('input_datetime.feed2') }},{{ states('input_select.feed_size_2') }}"
      l3: "{{ states('input_datetime.feed3') }},{{ states('input_select.feed_size_3') }}"
      l4: "{{ states('input_datetime.feed4') }},{{ states('input_select.feed_size_4') }}"
  - delay: 0:0:2
  - service: input_boolean.turn_off
    target:
      entity_id: input_boolean.update_feeder_schedule
  mode: single

My plan is to update this post with details on building the feeder. For now it’s just some hints.

Building the feeder

Parts

You can get the gimbal and 2 servos pre assembled here if you like.

These instructs provide access to the hat that controls the gimbal and GPIO interface for a motor to run the feeder, camera and a lazer.

After building loading raspberry pi os on the feeder by we need to install software that allows us to access the relay switch board. At the pi command prompt do the following:

  • sudo apt-get update
  • sudo apt-get install python-smbus i2c-tools
  • sudo pip install adafruit-pca9685
  • sudo pip3 install adafruit-pca9685
  • sudo pip3 install adafruit-circuitpython-servokit

You need to make sure i2c is enable on the Pi

  • sudo raspi-config
  • Select “Interfacing Options”
  • Select “I2C”
  • Select “Yes” to question “Would you like the ARM I2C interface enabled
  • Select “OK” once it tells you the interface is enabled
  • Select “Finish” to exit the tool
    Note: if it ask you if you want to reboot, respond yes

To check that the Pi sees the relay switch board do the following at the command promp

  • sudo i2cdetect -y 1

The response should show 40 and 70 in the 0 column like this:

image

More to come

update_feeder_schedule script that gets run by HA via ssh

#! /bin/bash
echo Input $1  > my.out
#IFS=, read t1 f1 t2 f2 t3 f3 t4 f4 <<< $1
#echo read t1=$t1  f1=$f1 t2=$t2 f2=$f2 t3=$t3 f3=$f3 t4=$t4 f4=$f4
IFS=, read -r -a fields <<< $1

: > mycrontab
firstField=1
for element in "${fields[@]}"
do
    if [ $firstField = 1 ]; then
        timeStr=$element
        IFS=: read hour minutes seconds <<< $timeStr
        firstField=0
    else
        #echo $hour, $minutes and $element
        firstField=1
        if [ $element = "s" ]; then
          echo "$minutes $hour * * * /var/www/html/macros/feed1.sh" >> mycrontab
        elif [ $element = "D" ]; then
          echo "$minutes $hour * * * /var/www/html/macros/feed2.sh" >> mycrontab
        fi
    fi
done

Main network script that controls feeder motor and gimbal

pi@raspberrypi:~/feeder_stuff $ cat networkControl.py
#!/usr/bin/python3
# Use PCA9685 PWM servo/LED controller library to control servos
# Author: Tony DiCola
# License: Public Domain
from __future__ import division
import time
import sys
import socket
import RPi.GPIO as GPIO
import random
import datetime
import math

from board import SCL, SDA
import busio
from adafruit_pca9685 import PCA9685
from adafruit_motor import servo

# Uncomment to enable debug output.
#import logging
#logging.basicConfig(level=logging.DEBUG)

freq = 60  # number of pulses per second 

VERT_CHAN = 1 
HORZ_CHAN = 2
FEED_CHAN = 7 
#HORZ_NEUT_OFFSET=15
HORZ_NEUT_OFFSET=(5)

feed_method='M'  # S for servo, anything else for motor
SECS_PER_SECTION=20  #  NUM of seconds it takes to move a section


vert_position = None 
horz_position = None
feed_position = None
horz_delay = .03
vert_delay = .03
feed_delay = .02

feed_min=0
feed_neutral=135
feed_max=270
#FEED_REC_SECS=120
FEED_REC_SECS=300

recording = False
recording = True

vert_min = 0
tpsg90_min = 0
tpsg90_max = 180 
tpsg90_neutral = 90

feed_vert_pos = tpsg90_max - 5
feed_horz_pos = tpsg90_neutral+HORZ_NEUT_OFFSET

rest_vert_pos = feed_vert_pos
rest_horz_pos = feed_horz_pos
#rest_vert_pos = tpsg90_neutral - 25
#rest_horz_pos = tpsg90_neutral+HORZ_NEUT_OFFSET

# the following positions assume starting at position 0
#feed_drops = [120, 188, 270, 120, 68, 0]
#feed_drops = [150, 210, 270, 120, 60, 0]
feed_drops = [140, 210, 270, 160, 75, 0]

GPIO_LASER = 27
GPIO_FEED_MOTOR = 17

def move_servo( servo, start, end, delay ):

    if start < end :
        inc = 1
    else:
        inc = -1
        end = end - 1

    for i in range( start,end,inc):
        #print("I = ",i)
        servo.angle = i
        time.sleep(delay)

def move_gimbal( hServo, hStart, hEnd, vServo, vStart, vEnd, delay ):

    if hStart < hEnd :
        hInc = 1
    else:
        hInc = -1
        hEnd = hEnd - 1
    hRange = abs( hEnd - hStart)

    if vStart < vEnd :
        vInc = 1
    else:
        vInc = -1
        vEnd = vEnd - 1
    vRange = abs( vEnd - vStart)

    incRange = hRange
    if vRange > hRange:
        incRange = vRange

    for i in range(1,incRange):
        #print("I = ",i)

        #update increments if we're still supposed to move
        if hStart != hEnd:
            hStart = hStart + (1*hInc)
        if vStart != vEnd:
            vStart = vStart + (1*vInc)

        hServo.angle = hStart
        vServo.angle = vStart
        time.sleep(delay)

    turn_off_servos()


def set_pulse_length( direction , step ):

    global vert_position 
    global horz_position
    global feed_position

    print( direction )

    if direction == 'u':
        start = vert_position
        vert_position =  vert_position - step
        if  vert_position < vert_min:
             vert_position = vert_min
        end = vert_position

        move_servo( vertServo, start, end , vert_delay)
        turn_off_servos()

    elif direction == 'd':
        start = vert_position
        vert_position =  vert_position + step
        if vert_position > tpsg90_max:
            vert_position = tpsg90_max 
        end = vert_position

        move_servo( vertServo, start, end , vert_delay)
        turn_off_servos()

    elif direction == 'r':
        start = horz_position
        horz_position =  horz_position - step
        if  horz_position < tpsg90_min:
             horz_position = tpsg90_min
        end = horz_position

        move_servo( horzServo, start, end , horz_delay)
        turn_off_servos()

    elif direction == 'l':
        start = horz_position
        horz_position =  horz_position + step
        if  horz_position > tpsg90_max:
             horz_position = tpsg90_max 
        end = horz_position

        move_servo( horzServo, start, end , horz_delay)
        turn_off_servos()

    elif direction == '+':
        start = feed_position
        feed_position =  feed_position + step
        if  feed_position > feed_max:
             feed_position = feed_max 
        end = feed_position

        move_servo( feedServo, start, end , feed_delay)
        turn_off_servos()

    elif direction == '-':
        start = feed_position
        feed_position =  feed_position - step
        if  feed_position < feed_min:
             feed_position = feed_min 
        end = feed_position

        move_servo( feedServo, start, end , feed_delay)
        turn_off_servos()

    elif direction == '0':
        start = feed_position
        feed_position =  feed_min 
        end = feed_position

        move_servo( feedServo, start, end , feed_delay)
        turn_off_servos()
    
    elif direction == '1':
        start = feed_position
        feed_position =  int(feed_max/3) 
        end = feed_position

        move_servo( feedServo, start, end , feed_delay)
        turn_off_servos()

    elif direction == '2':
        start = feed_position
        feed_position = int((feed_max/3)*2)
        end = feed_position

        move_servo( feedServo, start, end , feed_delay)
        turn_off_servos()

    elif direction == '3':
        start = feed_position
        feed_position =  feed_max 
        end = feed_position

        move_servo( feedServo, start, end , feed_delay)
        turn_off_servos()

    elif direction == 'N':
        feed_next_drop()

    #elif direction == 's':
    #    horz_position = tpsg90_neutral
    #    vert_position = tpsg90_max 

    #    pwm.set_pwm(VERT_CHAN, 0, vert_position);
    #    pwm.set_pwm(HORZ_CHAN, 0, horz_position);

    print('horizonal {0} : vertical {1} : feed {2}'.format(horz_position, vert_position, feed_position))


def feed_drop_servo():

    global feed_position

    with open("/home/pi/feeder_stuff/feed_position", "r+") as f:
        cur_pos = int(f.read()) 
        next_pos = cur_pos + 1

        if next_pos >= len(feed_drops):
            next_pos = 0

        cur_servo_val = feed_drops[ cur_pos ]
        next_servo_val = feed_drops[ next_pos ]

        print('Servo start {0} : Servo End {1}'.format(cur_servo_val,next_servo_val))

        move_servo( feedServo, cur_servo_val, next_servo_val , feed_delay)
        
        feed_position = next_servo_val

        turn_off_servos()

        f.seek(0)
        f.write(str(next_pos))
        f.truncate()
        f.close()


def feed_drop_motor():

    GPIO.output(GPIO_FEED_MOTOR, 1) # turn motor on
    time.sleep(SECS_PER_SECTION)  #  sleep time it takes to move a section
    GPIO.output(GPIO_FEED_MOTOR, 0) # turn motor on


def servo_rest_position():
    global vert_position 
    global horz_position
    global feed_position

    if vert_position is None:
        #start = tpsg90_neutral - 10
        start = rest_vert_pos
    else:
        start = vert_position
    vert_position = rest_vert_pos
    move_servo( vertServo, start, vert_position , vert_delay)
    print("Vert Server move: ",start, vert_position)


    if horz_position is None:
        start = tpsg90_neutral - 10
    else:
        start = horz_position

    horz_position = rest_horz_pos
    move_servo( horzServo, start, horz_position, horz_delay )
    print("Horz Server move: ",start, horz_position)

    turn_off_servos()

    if feed_position is None:
        with open("/home/pi/feeder_stuff/feed_position", "r") as f:
            cur_pos = int(f.read()) 
            feed_position = feed_drops[ cur_pos ]
            f.close()

def turn_off_servos():
    #pca.reset()
    horzServo._pwm_out.duty_cycle = 0
    vertServo._pwm_out.duty_cycle = 0
    feedServo._pwm_out.duty_cycle = 0
    print("Turn off servos")

def feedNeu( ):
        global feed_position
        # Move feeder to Neutral
        start = feed_position
        feed_position =  feed_neutral
        end = feed_position
        move_servo( feedServo, start, end ,feed_delay)
        turn_off_servos()

def feedMin( ):
        global feed_position
        # Move feeder to Neutral
        start = feed_position
        feed_position =  feed_min
        end = feed_position
        move_servo( feedServo, start, end , feed_delay)
        turn_off_servos()

def feedMax( ):
        global feed_position
        # Move feeder to Neutral
        start = feed_position
        feed_position =  feed_max
        end = feed_position
        move_servo( feedServo, start, end , feed_delay)
        turn_off_servos()


def feed( sections ):

    global vert_position 
    global horz_position
    global feed_position

    if recording :
        # set the camera to look at bowls

        start = horz_position 
        horz_position = feed_horz_pos
        move_servo( horzServo, start, horz_position , horz_delay)
        start = vert_position 
        vert_position = feed_vert_pos
        move_servo( vertServo, start, vert_position , vert_delay)
        turn_off_servos()

    # open FIFO used to start recording if need be
    with open('/var/www/html/FIFO1', 'w') as f:

        if recording :
            # Tell raspimjpeg scheduler to start recording
            f.write('1')
            f.flush()

        rectime=FEED_REC_SECS
        #Drop the food
        for i in range(0,sections,1):
            if feed_method == 'S':
                feed_drop_servo()
            else:
                feed_drop_motor()
                rectime-=SECS_PER_SECTION  # reduce time we'll wait to finish recording by time takes motor to move a section

        if recording :
            #Record a little bit
            time.sleep(rectime)
        
            # Tell raspimjpeg scheduler to stop recording
            f.write('0')
            f.flush()

        f.close()

    if recording :
        servo_rest_position()

def play( howlong ):

    global vert_position 
    global horz_position

    HLIMIT = 30 
    VLIMIT = 30


    #set range of motion in horizonal direction
    hmax = tpsg90_max - HLIMIT
    hmin = tpsg90_min + HLIMIT

    #set range of motion in virtual direction
    vmax = tpsg90_max - 30
    vmin = tpsg90_neutral +20 

    # cal range for mod function 
    hrange = hmax - hmin
    vrange = vmax - vmin


    # Move gimbal to starting position
    move_gimbal( horzServo, horz_position, tpsg90_neutral+HORZ_NEUT_OFFSET, vertServo, vert_position , vmin, horz_delay )
    # update position variables
    vert_position = vmin
    horz_position = tpsg90_neutral+HORZ_NEUT_OFFSET

    # turn laser on
    GPIO.output(GPIO_LASER, 1)

    #get time we're starting
    start_time = datetime.datetime.now()
    et = datetime.datetime.now() - start_time

    # we need to start video collection
    with open('/var/www/html/FIFO1', 'w') as f:

        # Tell raspimjpeg scheduler to start recording
        f.write('1')
        f.flush()


        while et.total_seconds() < howlong: 

            end_v = vmin + random.randint(0,vrange)
            end_h = hmin + random.randint(0,hrange)

            move_gimbal( horzServo, horz_position, end_h, vertServo, vert_position , end_v, horz_delay )

            horz_position = end_h
            vert_position = end_v

            # delay some after each move
            time.sleep(random.randint(1,4))

            et = datetime.datetime.now() - start_time


        # Tell raspimjpeg scheduler to stop recording
        f.write('0')
        f.flush()
        f.close()

    #turn off laser
    GPIO.output(GPIO_LASER, 0)
    servo_rest_position()

# -- Main ----
for arg in sys.argv[1:]:
    print( arg )

i2c = busio.I2C(SCL, SDA)

# Create a simple PCA9685 class instance.
pca = PCA9685(i2c)
pca.frequency = freq 

feedServo = servo.Servo(pca.channels[FEED_CHAN], actuation_range=feed_max, min_pulse=500, max_pulse=2500)

vertServo = servo.Servo(pca.channels[VERT_CHAN], min_pulse=500, max_pulse=2400)
horzServo = servo.Servo(pca.channels[HORZ_CHAN], min_pulse=500, max_pulse=2400)

# turn off servos
turn_off_servos()


servo_rest_position()

# Configure laser GPIO pin
GPIO.setmode(GPIO.BCM)
GPIO.setup(GPIO_LASER, GPIO.OUT)
#Make sure laser is turned off
GPIO.output(GPIO_LASER, 0)

#Setup GPIO for feed motor
GPIO.setup(GPIO_FEED_MOTOR, GPIO.OUT)
GPIO.output(GPIO_FEED_MOTOR, 0)
sys.stdout.flush()

HOST = ''                 # Symbolic name meaning all available interfaces
PORT = 33333              # Arbitrary non-privileged port
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen(1)

    try:
        while True:
            conn, addr = s.accept()
            with conn:
                print('Connected by', addr)
                while True:
                    data = conn.recv(1024)
                    if not data: break
                    print('Data: ',data)

                    if chr(data[0]) == 'f':
                        feed(1)
                    elif chr(data[0]) == 'F':
                        feed(2)
                    elif chr(data[0]) == 'n':
                        feedNeu()
                    elif chr(data[0]) == 'm':
                        feedMin()
                    elif chr(data[0]) == 'M':
                        feedMax()
                    elif chr(data[0]) == 'R':
                        if recording :
                            print("Disable recording on feed")
                            recording = False
                        else:
                            print("Enable recording on feed")
                            recroding = True
                    elif chr(data[0]) == 'p':
                        #play( 180 )
                        play( 120 )
                    else:
                        set_pulse_length( chr(data[0]) , 20 )

                conn.close()
                sys.stdout.flush()
    finally:
        GPIO.output(GPIO_LASER, 0)
        GPIO.output(GPIO_FEED_MOTOR, 0)
        GPIO.cleanup()
        # turn off servos
        turn_off_servos()
pi@raspberrypi:~/feeder_stuff $ 
2 Likes

This is awesome.

I would just mount a standalone camera where you took the photo from to simplify the setup. But I’m lazy.

I built one of these for my daughter. To be honest part of my motivation for adding the pan and tilt was to provide a security feature for my daughter. If she came home and was spooked about something, before entering her apartment the camera could be used to do a quick scan. At one point I had motion detection with object identification in the package. The cats also responded well to the laser pointer at first, that was before we added food into the machine. Once we started using it to feed them, when they heard the camera move they though food might fall, so they were less interested in chasing the laser.