Stupid Pet Trick: Nodding Cockatoo

If you experience difficulties in figuring out the shape, stand three meters away, squint your eyes, tilt your head to the left, and make a 196° degree turn on your tiptoes. That should do it.

The prompt for this project, creating a “stupid pet trick” incorporating analog and digital input and output, made me think of a stupid pet trick from my own home. This is Sisi, my pet cockatoo named after Empress Elizabeth of Austria (fun fact), who imitates others’ nodding motions as shown in the following video:

Guests always find the interaction with Sisi amusing, more so if they whistle and she “replies” with a whistle of her own. Knowing that this is considered a fun trick, I decided to simulate Sisi’s nodding and sounds (which I wasn’t able to get to, unfortunately). For this purpose, I considered using a Neopixels grid on which I could alternate different images or frames of a cockatoo, each in a different position, such that when changing rapidly the Neopixels create the illusion of a moving bird. These changes would be triggered by a distance (IR rangefinder) analog sensor, to detect whether the user moves her/his head. The sensor would thus aid in determining which cockatoo frame to use at each moment.

I couldn’t incorporate sound by the deadline, but I had thought of placing a piezo buzzer behind the Neopixels grid and a microphone near the distance sensor. If the user were to speak near the cockatoo, the bird would “respond” with whistling sounds (the idea could be better implemented with actual recordings of my cockatoo, instead of the tones produced by the buzzer, as suggested by my professor).

The code is as follows, based on the Adafruit Neopixel “simple” example code by Shae Erisson:

#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
 #include <avr/power.h>
#endif

const int PIN = 6;
//output for Neopixels strip

const int NUMPIXELS = 72;
//grid has dimensions 8 x 9 pixels (72 LEDs)

Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

const int sensor = A5;
//analog input from IR rangefinder

//the following constants are three matrices, each representing one frame (the cockatoo at its highest, in the center or middle position, and at its lowest (the three positions together simulate the nodding motion) 
//each array within the larger array represents one LED, of which its color is defined by RGB values
const int birdUp[][3] = {
 //row 1
 {255, 255, 255},
 {255, 255, 255},
 {255, 127, 39},
 {255, 127, 39},
 {255, 127, 39},
 {0, 0, 0},
 {0, 0, 0},
 {0, 0, 0},

//row 2
 {0, 0, 0},
 {0, 0, 0},
 {255, 255, 255},
 {255, 255, 255},
 {255, 255, 255},
 {255, 255, 255},
 {255, 255, 255},
 {0, 0, 0},

//row 3
 {0, 0, 0},
 {255, 255, 255},
 {255, 255, 255},
 {255, 255, 255},
 {255, 255, 255},
 {255, 255, 255},
 {0, 0, 0},
 {0, 0, 0},

//row 4
 {0, 0, 0},
 {0, 0, 0},
 {255, 255, 255},
 {255, 255, 255},
 {255, 255, 255},
 {255, 255, 255},
 {255, 255, 255},
 {0, 0, 0},

//row 5
 {0, 0, 0},
 {0, 0, 0},
 {255, 255, 255},
 {255, 255, 255},
 {255, 255, 255},
 {255, 255, 255},
 {0, 0, 0},
 {0, 0, 0},

//row 6
 {0, 0, 0},
 {0, 162, 232},
 {0, 162, 232},
 {0, 162, 232},
 {255, 255, 255},
 {0, 0, 0},
 {0, 0, 0},
 {0, 0, 0},
 
 //row 7
 {0, 0, 0},
 {0, 0, 0},
 {0, 0, 0},
 {255, 255, 255},
 {0, 162, 232},
 {140, 39, 241},
 {0, 162, 232},
 {255, 127, 39},

//row 8
 {0, 0, 0},
 {0, 162, 232},
 {0, 162, 232},
 {0, 162, 232},
 {255, 255, 255},
 {255, 242, 0},
 {0, 0, 0},
 {0, 0, 0},

//row 9
 {0, 0, 0},
 {255, 242, 0},
 {255, 242, 0},
 {255, 242, 0},
 {255, 255, 255},
 {255, 255, 255},
 {0, 0, 0},
 {0, 0, 0}
 };

const int birdMiddle[][3] = {
 //row 1
 {255, 255, 255},
 {255, 255, 255},
 {255, 127, 39},
 {255, 127, 39},
 {255, 127, 39},
 {0, 0, 0},
 {0, 0, 0},
 {0, 0, 0},

//row 2
 {0, 0, 0},
 {0, 0, 0},
 {255, 255, 255},
 {255, 255, 255},
 {255, 255, 255},
 {255, 255, 255},
 {255, 255, 255},
 {0, 0, 0},

//row 3
 {0, 0, 0},
 {255, 255, 255},
 {255, 255, 255},
 {255, 255, 255},
 {255, 255, 255},
 {255, 255, 255},
 {0, 0, 0},
 {0, 0, 0},

//row 4
 {0, 0, 0},
 {0, 0, 0},
 {255, 255, 255},
 {255, 255, 255},
 {255, 255, 255},
 {255, 255, 255},
 {0, 0, 0},
 {0, 0, 0},

//row 5
 {0, 0, 0},
 {0, 0, 0},
 {0, 0, 0},
 {255, 255, 255},
 {0, 162, 232},
 {0, 162, 232},
 {0, 162, 232},
 {0, 0, 0},
 
 //row 6
 {255, 127, 39},
 {0, 162, 232},
 {140, 39, 241},
 {0, 162, 232},
 {255, 255, 255},
 {0, 0, 0},
 {0, 0, 0},
 {0, 0, 0},

//row 7
 {0, 0, 0},
 {0, 0, 0},
 {255, 242, 0},
 {255, 255, 255},
 {0, 162, 232},
 {0, 162, 232},
 {0, 162, 232},
 {0, 0, 0},

//row 8
 {0, 0, 0},
 {0, 0, 0},
 {255, 255, 255},
 {255, 255, 255},
 {255, 242, 0},
 {255, 242, 0},
 {255, 242, 0},
 {0, 0, 0},

//row 9
 {0, 0, 0},
 {0, 0, 0},
 {0, 0, 0},
 {0, 0, 0},
 {0, 0, 0},
 {0, 0, 0},
 {0, 0, 0},
 {0, 0, 0}
 };

const int birdDown[][3] = {
 //row 1
 {255, 255, 255},
 {255, 255, 255},
 {255, 127, 39},
 {255, 127, 39},
 {255, 127, 39},
 {0, 0, 0},
 {0, 0, 0},
 {0, 0, 0},

//row 2
 {0, 0, 0},
 {0, 0, 0},
 {255, 255, 255},
 {255, 255, 255},
 {255, 255, 255},
 {255, 255, 255},
 {255, 255, 255},
 {0, 0, 0},

//row 3
 {0, 0, 0},
 {0, 0, 0},
 {255, 255, 255},
 {255, 255, 255},
 {255, 255, 255},
 {255, 255, 255},
 {0, 0, 0},
 {0, 0, 0},

//row 4
 {0, 0, 0},
 {0, 162, 232},
 {0, 162, 232},
 {0, 162, 232},
 {255, 255, 255},
 {0, 0, 0},
 {0, 0, 0},
 {0, 0, 0},
 
 //row 5
 {0, 0, 0},
 {0, 0, 0},
 {0, 0, 0},
 {255, 255, 255},
 {0, 162, 232},
 {140, 39, 241},
 {0, 162, 232},
 {255, 127, 39},

//row 6
 {0, 0, 0},
 {0, 162, 232},
 {0, 162, 232},
 {0, 162, 232},
 {255, 255, 255},
 {255, 242, 0},
 {0, 0, 0},
 {0, 0, 0},

//row 7
 {0, 0, 0},
 {255, 242, 0},
 {255, 242, 0},
 {255, 242, 0},
 {255, 255, 255},
 {255, 255, 255},
 {0, 0, 0},
 {0, 0, 0},

//row 8
 {0, 0, 0},
 {0, 0, 0},
 {0, 0, 0},
 {0, 0, 0},
 {0, 0, 0},
 {0, 0, 0},
 {0, 0, 0},
 {0, 0, 0},

//row 9
 {0, 0, 0},
 {0, 0, 0},
 {0, 0, 0},
 {0, 0, 0},
 {0, 0, 0},
 {0, 0, 0},
 {0, 0, 0},
 {0, 0, 0}
 };

void setup() {
 pixels.begin();
}

void loop() {
 int distance = analogRead(sensor);
 int distanceNum = round(distance/100);
 //the sensor gives values ranging from around 250 to 650
 //divided by 100 and using the round() function, these values range from 3 to 7, which are more convenient for the program
 if(distanceNum > 4){
 for(int i=0;i<NUMPIXELS;i++){
 int red = birdUp[i][0];
 int green = birdUp[i][1];
 int blue = birdUp[i][2];
 pixels.setPixelColor(i, pixels.Color(red, green, blue));
 pixels.show();
 }
 }
 //if the sensor's value is 5, 6, or 7, the cockatoo appears at its highest (I wanted the range of the highest height to be the largest, given that it is easiest to make out the shape of the bird in this frame)
 else if(distanceNum == 4){
 for(int i=0;i<NUMPIXELS;i++){
 int red = birdMiddle[i][0];
 int green = birdMiddle[i][1];
 int blue = birdMiddle[i][2];
 pixels.setPixelColor(i, pixels.Color(red, green, blue));
 pixels.show();
 }
 }
 //if the sensor's value is 4, then the cockatoo appears at its middle height
 else{
 for(int i=0;i<NUMPIXELS;i++){
 int red = birdDown[i][0];
 int green = birdDown[i][1];
 int blue = birdDown[i][2];
 pixels.setPixelColor(i, pixels.Color(red, green, blue));
 pixels.show();
 }
 }
 //if the sensor's value is 3, then the cockatoo appears at its lowest height
 }

To create the grid, I connected nine strips of eight Neopixels each with soldered wires.

This is how the strips of Neopixels looked after I joined them, placed on a beautiful background that steals the show.

I found the image that I “translated” into the LEDs online. I edited it such that it was easy to pixelate, and altered the colors so that they didn’t appear to blend (similar color tones are difficult to distinguish for adjacent Neopixels). These four images demonstrate the process to reach the ideal pixelated cockatoo:

I placed the grid on a piece of wood, and created a pixelated tree trunk (made of colored craft wood sticks) under it, to give the illusion that the cockatoo is perched on a tree. The grid is covered with a somewhat translucent paper, to diffuse the lights and make the image’s recognition easier. The distance between the paper and the lights aids in the diffusion, and it would have been better to increase it just a bit more in this case. Nevertheless, the image turned out to be clear enough. The following picture shows the lit cockatoo (at its highest position, determined by one of the “frames”) over the tree. The distortion of the lights was caused by my phone’s camera; the cockatoo’s shape is actually clearer.

This is the complete setup, where the distance sensor is placed on the white box on the left, the Neopixels grid is on the right, and the RedBoard that connects both (as well as the laptop that powers it) is on the chair in the middle.


The sensor is placed in this arrangement because of two reasons. I chose to put the sensor inside the frame because it allows me to control the  values obtained from it. IR rangefinders work in such a way that after a certain point, the values it reads are reflected. For instance, if I place my hand over the sensor and gradually raise it, so as to increase the distance between the two, the values might read 250, …, 300, …, 400,… 500, …, 600, …, 500, … 400, …, 300, …, 250, … Thus, they are reflected around about 600, and the sensor cannot straightforwardly differentiate between the repeated values. The frame limits the sensor’s readings from about 250 to 650, thus eliminating the problem of the reflected values.

The sensor is also on top of a large box because I considered having the user move her/his head to trigger the cockatoo’s motion. Thus, I would need to have a structure that allowed this movement to occur comfortably. Just the frame wouldn’t have done it: placing it on a table, for example, would have required the user the bend in order to nod over the sensor. The tall box facilitated this aspect of the interaction. However, I decided to switch the motion from the head to the hands. Firstly, not all users’ heads will fit inside the frame, and even if they do, the movement is restricted. Secondly, it is easier to control the hand’s placement over the sensor’s beam, which is quite narrow, than the head’s.

This is the cockatoo as it nods, activated by a hand: