Train your cat with the help of the Arduino IoT Cloud!
If you ever tried to train a cat, you know how hard it is. Cats are their own masters, but now you have the chance to let the cats do your bidding using this IoT-enabled device.
Welcome to Pavlov's Cat Experiment!
In this project, you will learn how to teach your cat when it is (and isn't) dinner time using nothing but the components in the IoT Bundle and some cardboard.
And we all know that cats already love cardboard boxes! Every time the cat hears a certain melody, it receives food. A light sensor detects the presence of a cat.
Another melody does nothing. See how this will work? You will be able to monitor your cat's progression over time and set the food dispensing rate from your phone.
Disclaimer: No cats were hurt in the development of this experiment. Also, no guarantee that the cat will eat the food, but you get the picture, right?
You will be able to build your own food dispenser by following these simple step-by-step instructions. The dispenser is basically just some cardboard and a servo motor with some added Arduino magic.
Using the Arduino IoT Cloud Dashboard, you can set the amount of food to be dispensed and trigger the melodies played with the buzzer.
A light sensor is used to detect if the cat reacted to the melody and got to the food.
Get the cardboard blueprint for this project here!
In this experiment you will learn how to:
This tutorial is part of a series of experiments that familiarize you with the Arduino RP2040 and IoT. All experiments can be built using the components contained in the IoT Bundle.
If you are new to the Arduino IoT Cloud, check out our Getting Started Guide.
To connect your board to the Arduino IoT Cloud, we will use the Pavlov's Cat Template. This template installs a specific sketch on your board and creates a dashboard that allows you to interact with your board: you don't need to write any code at all!
See the image below to understand how to set it up.
We will start by setting up the Arduino IoT Cloud by following the steps below:
We wills start by adding four variables:
Play the Song
The next section explains how to play a melody using a piezo buzzer and your Arduino.
Connect the buzzer to digital pin 7, as shown in the picture above. Now, navigate into the Arduino Web Editor through Thing > Sketch tab > open full editor. This will open up our automatically generated sketch in the full Arduino Web Editor. Next we need to add an extra tab containing all the necessary notes to play the song. Click the arrow on the right side to add a new tab called
"pitches.h"
. See the GIF below for more detailed steps:Paste the following code into
"pitches.h"
.1#define NOTE_B0 312#define NOTE_C1 333#define NOTE_CS1 354#define NOTE_D1 375#define NOTE_DS1 396#define NOTE_E1 417#define NOTE_F1 448#define NOTE_FS1 469#define NOTE_G1 4910#define NOTE_GS1 5211#define NOTE_A1 5512#define NOTE_AS1 5813#define NOTE_B1 6214#define NOTE_C2 6515#define NOTE_CS2 6916#define NOTE_D2 7317#define NOTE_DS2 7818#define NOTE_E2 8219#define NOTE_F2 8720#define NOTE_FS2 9321#define NOTE_G2 9822#define NOTE_GS2 10423#define NOTE_A2 11024#define NOTE_AS2 11725#define NOTE_B2 12326#define NOTE_C3 13127#define NOTE_CS3 13928#define NOTE_D3 14729#define NOTE_DS3 15630#define NOTE_E3 16531#define NOTE_F3 17532#define NOTE_FS3 18533#define NOTE_G3 19634#define NOTE_GS3 20835#define NOTE_A3 22036#define NOTE_AS3 23337#define NOTE_B3 24738#define NOTE_C4 26239#define NOTE_CS4 27740#define NOTE_D4 29441#define NOTE_DS4 31142#define NOTE_E4 33043#define NOTE_F4 34944#define NOTE_FS4 37045#define NOTE_G4 39246#define NOTE_GS4 41547#define NOTE_A4 44048#define NOTE_AS4 46649#define NOTE_B4 49450#define NOTE_C5 52351#define NOTE_CS5 55452#define NOTE_D5 58753#define NOTE_DS5 62254#define NOTE_E5 65955#define NOTE_F5 69856#define NOTE_FS5 74057#define NOTE_G5 78458#define NOTE_GS5 83159#define NOTE_A5 88060#define NOTE_AS5 93261#define NOTE_B5 98862#define NOTE_C6 104763#define NOTE_CS6 110964#define NOTE_D6 117565#define NOTE_DS6 124566#define NOTE_E6 131967#define NOTE_F6 139768#define NOTE_FS6 148069#define NOTE_G6 156870#define NOTE_GS6 166171#define NOTE_A6 176072#define NOTE_AS6 186573#define NOTE_B6 197674#define NOTE_C7 209375#define NOTE_CS7 221776#define NOTE_D7 234977#define NOTE_DS7 248978#define NOTE_E7 263779#define NOTE_F7 279480#define NOTE_FS7 296081#define NOTE_G7 313682#define NOTE_GS7 332283#define NOTE_A7 352084#define NOTE_AS7 372985#define NOTE_B7 395186#define NOTE_C8 418687#define NOTE_CS8 443588#define NOTE_D8 469989#define NOTE_DS8 497890/* notes in the melody */91int melodyOne[] = {92NOTE_C4, NOTE_G3, NOTE_G3, NOTE_A3, NOTE_G3, 0, NOTE_B3, NOTE_C493};94/* note durations: 4 = quarter note, 8 = eighth note, etc. */95int noteDurationsOne[] = {964, 8, 8, 4, 4, 4, 4, 497};98int melodyTwo[] = {99NOTE_C5, NOTE_C5, NOTE_A4, NOTE_C5, 0, NOTE_G4, NOTE_C5, NOTE_F5, NOTE_E5, NOTE_C5, NOTE_C5100};101/* note durations: 4 = quarter note, 8 = eighth note, etc. */102int noteDurationsTwo[] = {1032, 2, 4, 4, 8, 4, 4, 4, 4, 4 , 4104};
Now, we need to go back to our main sketch to play the melody. In order to use the notes we need to add
#include "pitches.h"
at the top of our code.Inside setup() we need to add our buzzerPin as Output. At the bottom of the code we will add a function called playMelody() with three parameters: melody,durations and numberOfNotes.
To test our melody we can call the playMelody function inside loop() with our parameters in place.
1void loop() {2 playMelody(melodyOne, noteDurationsOne, 8);3 delay(5000);4}
Copy the code below to test the melody
1#include "thingProperties.h"2#include "pitches.h"3int buzzerPin = 7;4void setup() {5 /* Initialize serial and wait for port to open: */6 Serial.begin(9600);7 /* This delay gives the chance to wait for a Serial Monitor without blocking if none is found */8 delay(1500);9 pinMode(buzzerPin, OUTPUT);10 /* Defined in thingProperties.h */11 initProperties();12 /* Connect to Arduino IoT Cloud */13 ArduinoCloud.begin(ArduinoIoTPreferredConnection);14 15/*16The following function allows you to obtain more information17related to the state of network and IoT Cloud connection and errors18the higher number the more granular information you’ll get.19The default is 0 (only errors).20Maximum is 421*/22setDebugMessageLevel(2);23ArduinoCloud.printDebugInfo();24}25void loop() {26 ArduinoCloud.update();27 playMelody(melodyOne, noteDurationsOne, 8);28 delay(5000);29}30void playMelody(int melody[], int noteDurations[], int numberOfNotes ) {31 Serial.println("Playing melody");32 for (int thisNote = 0; thisNote < numberOfNotes; thisNote++) {33 /* to calculate the note duration, take one second divided by the note type.34 e.g. quarter note = 1000 / 4, eighth note = 1000/8, etc. */35 int noteDuration = 1000 / noteDurations[thisNote];36 tone(buzzerPin, melody[thisNote], noteDuration);37 /* to distinguish the notes, set a minimum time between them.38 the note's duration + 30% seems to work well */39 int pauseBetweenNotes = noteDuration * 1.30;40 delay(pauseBetweenNotes);41 /* stop the tone playing */42 noTone(buzzerPin); 43 }44}45/*46Since Notification is READ_WRITE variable, onNotificationChange() is47executed every time a new value is received from IoT Cloud.48*/49void onNotificationChange() {50/* Add your code here to act upon Notification change */51}52/*53Since SendData is READ_WRITE variable, onSendDataChange() is54executed every time a new value is received from IoT Cloud.55*/56void onSendDataChange() {57/* Add your code here to act upon SendData change */58}59/*60Since Portion is READ_WRITE variable, onPortionChange() is61executed every time a new value is received from IoT Cloud.62*/63void onPortionChange() {64/* Add your code here to act upon Portion change */65}66/*67Since Melody is READ_WRITE variable, onMelodyChange() is68executed every time a new value is received from IoT Cloud.69*/70void onMelodyChange() {71/* Add your code here to act upon Melody change */72}
Detect the Cat!
In order to detect the presence of the cat we will use a phototransistor, which is able to measure the light intensity and therefore if someone has passed close to it. Connect the sensor to the A0 pin as shown below.
Note that we used a 220 ohm resistor.
To read values from the sensor we will only need to assign a variable to analogRead(A0). Since we are interested in detecting the cat presence only after the melody, and just for a certain amount of time we can use the following logic:
1#include "thingProperties.h"2#include "pitches.h"3int buzzerPin = 7;4unsigned long timer;5bool startDetecting = true;6int threshold = 200;7void setup() {8 /* Initialize serial and wait for port to open: */9 Serial.begin(9600);10 /* This delay gives the chance to wait for a Serial Monitor without blocking if none is found */11 delay(1500);12 13 pinMode(buzzerPin, OUTPUT);14 timer = millis();15 16 /* Defined in thingProperties.h */17 initProperties();18 19 /* Connect to Arduino IoT Cloud */20 ArduinoCloud.begin(ArduinoIoTPreferredConnection);21/*22The following function allows you to obtain more information23related to the state of network and IoT Cloud connection and errors24the higher number the more granular information you’ll get.25The default is 0 (only errors).26Maximum is 427*/28setDebugMessageLevel(2);29ArduinoCloud.printDebugInfo();30}31void loop() {32 ArduinoCloud.update();33 34 if (startDetecting) {35 int value = analogRead(A0);36 if (value < threshold) {37 Serial.println("cat detected!");38 startDetecting = false;39 } else if (millis() - timer < 12000) {40 Serial.println("no cat detected in the past two minutes");41 startDetecting = false;42 }43 }44}45void playMelody(int melody[], int noteDurations[], int numberOfNotes ) {46 Serial.println("Playing melody");47 for (int thisNote = 0; thisNote < numberOfNotes; thisNote++) {48 /* to calculate the note duration, take one second divided by the note type.49 e.g. quarter note = 1000 / 4, eighth note = 1000/8, etc. */50 int noteDuration = 1000 / noteDurations[thisNote];51 tone(buzzerPin, melody[thisNote], noteDuration);52 /* to distinguish the notes, set a minimum time between them.53 the note's duration + 30% seems to work well */54 int pauseBetweenNotes = noteDuration * 1.30;55 delay(pauseBetweenNotes);56 /* stop the tone playing */57 noTone(buzzerPin); 58 }59}60/*61Since Notification is READ_WRITE variable, onNotificationChange() is62executed every time a new value is received from IoT Cloud.63*/64void onNotificationChange() {65/* Add your code here to act upon Notification change */66}67/*68Since SendData is READ_WRITE variable, onSendDataChange() is69executed every time a new value is received from IoT Cloud.70*/71void onSendDataChange() {72/* Add your code here to act upon SendData change */73}74/*75Since Portion is READ_WRITE variable, onPortionChange() is76executed every time a new value is received from IoT Cloud.77*/78void onPortionChange() {79/* Add your code here to act upon Portion change */80}81/*82Since Melody is READ_WRITE variable, onMelodyChange() is83executed every time a new value is received from IoT Cloud.84*/85void onMelodyChange() {86/* Add your code here to act upon Melody change */87}
Note that we use themillis()function to set a timer.
millis()
gives us the time in milliseconds since the board was up and running. We can use it to set timers and trigger events after a certain amount of time.We also use a threshold to determine if the cat was detected. That threshold is arbitrary, you can set it according to your light condition.
Add the Servo Motor
Note: for the servo motor you will need a 9V battery which is not included in the IoT Bundle! Alternatively you can use another external power supply such as a phone charger with open ended cables.
The servo is used to open the box and deliver food. Note that we will use the portion variable to set the amount of time the servo has to remain turned 90 degrees. We will be able to change the portion value through the Arduino IoT Cloud Dashboard.
Attach the servo to pin 6 as shown above. To control the will make us of the servo library by adding
#include "servo.h"
at the top. We also need to initialize a variable setting the starting position of the servo by adding Servo myservo
under our libraries and inside setup()
add myservo.attach(6)
which tells the program which pin we are using.Inside the loop() function we also need to change some things as we put the project together. We only want to dispense food if the portion size is set and the melody has been played. We also want to start detecting if a cat is approaching and log the time it needed to arrive to the serial port.
Finally, we can add a function below
playMelody()
called moveServo()
.1#include "thingProperties.h"2#include "pitches.h"3#include "Servo.h"4Servo myservo;5int pos = 0;6int buzzerPin = 7;7unsigned long timer;8bool startDetecting = true;9int threshold = 200;10void setup() {11 /* Initialize serial and wait for port to open: */12 Serial.begin(9600);13 /* This delay gives the chance to wait for a Serial Monitor without blocking if none is found */14 delay(1500);15 16 pinMode(buzzerPin, OUTPUT);17 timer = millis();18 19 /* Defined in thingProperties.h */20 initProperties();21 22 /* Connect to Arduino IoT Cloud */23 ArduinoCloud.begin(ArduinoIoTPreferredConnection);24/*25The following function allows you to obtain more information26related to the state of network and IoT Cloud connection and errors27the higher number the more granular information you’ll get.28The default is 0 (only errors).29Maximum is 430*/31setDebugMessageLevel(2);32ArduinoCloud.printDebugInfo();33}34void loop() {35 ArduinoCloud.update();36 if (sendData) { /* Checks if there are some updates */37 if (melody) {38 if (portion > 0) { /* if Melody AND food */39 notification = "Dispensing " + String(portion) + " portion of food right now"; /* notification of dispensed food */40 playMelody(melodyOne, noteDurationsOne, 8);41 moveServo();42 startDetecting = true;43 timer = millis();44 } else if (portion == 0) { /* if Melody and NO food */45 notification = "At your command";46 playMelody(melodyTwo, noteDurationsTwo, 11);47 startDetecting = true;48 timer = millis();49 }50 } else if (!melody) {51 notification = "Hello! \n Please turn the melody ON to train your cat";52 }53}54if (startDetecting) {55 int value = analogRead(A0);56 if (value < 200) {57 String TimeValue = String((millis() - timer) / 1000);58 notification = "Cat detected! \nTime to reach the feeder: " + TimeValue + " seconds";59 startDetecting = false;60 } else if (millis() - timer > 120000) {61 notification= "No cat detected in the past two minutes";62 startDetecting = false;63 }64}65delay(1000);66}67void playMelody(int melody[], int noteDurations[], int numberOfNotes ) {68 Serial.println("Playing melody");69 for (int thisNote = 0; thisNote < numberOfNotes; thisNote++) {70 /* to calculate the note duration, take one second divided by the note type.71 e.g. quarter note = 1000 / 4, eighth note = 1000/8, etc. */72 int noteDuration = 1000 / noteDurations[thisNote];73 tone(buzzerPin, melody[thisNote], noteDuration);74 /* to distinguish the notes, set a minimum time between them.75 the note's duration + 30% seems to work well */76 int pauseBetweenNotes = noteDuration * 1.30;77 delay(pauseBetweenNotes);78 /* stop the tone playing */79 noTone(buzzerPin); 80 }81}82void moveServo() {83 if (portion > 0){84 Serial.println("moving servo");85 for (pos = 0; pos <= 90; pos += 1) {86 /* goes from 0 degrees to 90 degrees */87 myservo.write(pos); /* tell servo to go to position in variable 'pos' */88 delay(15); /* waits 15ms for the servo to reach the position */89 }90 delay(portion * 300); /* keep the box open for a time interval based on the amount of food you want to deliver */91 for (pos = 90; pos >= 0; pos -= 1) {92/* goes from 90 degrees to 0 degrees */93 myservo.write(pos); /* tell servo to go to position in variable 'pos' */94 delay(15); /* waits 15ms for the servo to reach the position */95 }96 }97}98/*99Since Notification is READ_WRITE variable, onNotificationChange() is100executed every time a new value is received from IoT Cloud.101*/102void onNotificationChange() {103/* Add your code here to act upon Notification change */104}105/*106Since SendData is READ_WRITE variable, onSendDataChange() is107executed every time a new value is received from IoT Cloud.108*/109void onSendDataChange() {110/* Add your code here to act upon SendData change */111}112/*113Since Portion is READ_WRITE variable, onPortionChange() is114executed every time a new value is received from IoT Cloud.115*/116void onPortionChange() {117/* Add your code here to act upon Portion change */118}119/*120Since Melody is READ_WRITE variable, onMelodyChange() is121executed every time a new value is received from IoT Cloud.122*/123void onMelodyChange() {124/* Add your code here to act upon Melody change */125}
The final step to deploying our project is adding a control panel using the Arduino IoT Dashboards. We can navigate to Dashboards > Build Dashboard > ADD, then we can add four widget and link them to the variable as the following:
Congratulations! Now you can upload the full sketch below and test your Pavlov Cat Machine!
This tutorial is part of a series of experiments that familiarize you with the Arduino IoT Bundle. All experiments can be built using the components contained in the IoT Bundle.