It all started, as most great projects do, with a bit of a cock-up. My 3D printer was acting up, and I had a dusty old Elegoo Arduino Uno R3 kit lying about. I needed some test prints, and somehow, instead of a boring calibration cube, I ended up with Project R – a hilariously grumpy mini companion bot with a proper attitude.
Born from leftover parts and a printing mishap, this little fella is now a full-blown diva on my desk.
The Bits and Bobs: What Makes R Tick?
Project R might look like a simple bundle of wires and plastic, but it's packed with personality. Here’s the lowdown on the hardware that gives it life:
Judgemental Eyes: An ultrasonic sensor mounted on a 9g servo lets it scan the room, turning its head like a curious (and slightly disapproving) little owl.
Expressive Arms: Its left arm waves thanks to a powerful little stepper motor, while the right arm, powered by another servo, is perfect for a cheeky wiggle or a full-on tantrum.
A Face Full of Sass: A glorious 8x8 LED matrix on its chest displays messages and a whole range of emoji-like faces, from 'curious' to 'thoroughly unimpressed'.
The Voice of an Angel (sort of): A simple buzzer provides the beeps, boops, and dramatic sound effects for its many moods.
The Brains of the Beast: How It Thinks
This is where the real magic happens. R isn't just running random animations. It feels. It sees. And it definitely judges.
Using its ultrasonic sensor, it knows how close you are. And it doesn't just see you; it times you. Stand in front of it for two seconds, and you might get a friendly wave. Linger for ten seconds, and it might get a bit too familiar and share some gossip.
The best part? If you ignore it for too long, it gets bored. After a minute of being left alone, it'll start sighing, drooping its head, and displaying bored faces, complete with sleepy "ZZZ" sounds. With over 160 unique, pre-programmed reactions combining messages, emojis, movements, and sounds, you never quite know what you're going to get. It’s less of a desk toy and more of a tiny, moody flatmate.
#include <LedControl.h>
#include <Stepper.h>
#include <Servo.h>
#include <avr/pgmspace.h>
// Pin definitions
const int DIN = 11; // LED Matrix DIN
const int CS = 12; // LED Matrix CS
const int CLK = 13; // LED Matrix CLK
const int STEPPER_IN1 = 10; // Left Arm Stepper
const int STEPPER_IN2 = 9;
const int STEPPER_IN3 = 7;
const int STEPPER_IN4 = 8;
const int ECHO = 5; // Ultrasonic Sensor Echo
const int TRIG = 6; // Ultrasonic Sensor Trig
const int SERVO_RIGHT_ARM = 4; // Right Arm Servo
const int BUZZER = 3; // Buzzer
const int SERVO_HEAD = 2; // Head Servo
// Component objects
LedControl lc = LedControl(DIN, CLK, CS, 1);
Stepper leftArmStepper = Stepper(2048, STEPPER_IN1, STEPPER_IN3, STEPPER_IN2, STEPPER_IN4);
Servo rightArmServo;
Servo headServo;
// 8x8 font (A-Z, space), row-major, MSB left
const byte font[27][8] PROGMEM = {
{0x00, 0x38, 0x44, 0x44, 0x7C, 0x44, 0x44, 0x00}, // A
{0x00, 0x7C, 0x44, 0x44, 0x78, 0x44, 0x7C, 0x00}, // B
{0x00, 0x7C, 0x40, 0x40, 0x40, 0x40, 0x7C, 0x00}, // C
{0x00, 0x78, 0x44, 0x44, 0x44, 0x44, 0x78, 0x00}, // D
{0x00, 0x7C, 0x40, 0x7C, 0x40, 0x40, 0x7C, 0x00}, // E
{0x00, 0x7C, 0x40, 0x7C, 0x40, 0x40, 0x40, 0x00}, // F
{0x00, 0x7C, 0x40, 0x40, 0x4C, 0x44, 0x7C, 0x00}, // G
{0x00, 0x44, 0x44, 0x7C, 0x44, 0x44, 0x44, 0x00}, // H
{0x00, 0x7C, 0x10, 0x10, 0x10, 0x10, 0x7C, 0x00}, // I
{0x00, 0x04, 0x04, 0x04, 0x04, 0x44, 0x7C, 0x00}, // J
{0x00, 0x44, 0x48, 0x70, 0x48, 0x44, 0x44, 0x00}, // K
{0x00, 0x40, 0x40, 0x40, 0x40, 0x40, 0x7C, 0x00}, // L
{0x00, 0x44, 0x6C, 0x54, 0x44, 0x44, 0x44, 0x00}, // M
{0x00, 0x44, 0x64, 0x54, 0x4C, 0x44, 0x44, 0x00}, // N
{0x00, 0x7C, 0x44, 0x44, 0x44, 0x44, 0x7C, 0x00}, // O
{0x00, 0x7C, 0x44, 0x44, 0x7C, 0x40, 0x40, 0x00}, // P
{0x00, 0x7C, 0x44, 0x44, 0x4C, 0x48, 0x7C, 0x00}, // Q
{0x00, 0x7C, 0x44, 0x44, 0x78, 0x44, 0x44, 0x00}, // R
{0x00, 0x7C, 0x40, 0x7C, 0x04, 0x04, 0x7C, 0x00}, // S
{0x00, 0x7C, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00}, // T
{0x00, 0x44, 0x44, 0x44, 0x44, 0x44, 0x7C, 0x00}, // U
{0x00, 0x44, 0x44, 0x44, 0x44, 0x28, 0x10, 0x00}, // V
{0x00, 0x44, 0x44, 0x54, 0x54, 0x6C, 0x44, 0x00}, // W
{0x00, 0x44, 0x28, 0x10, 0x28, 0x44, 0x44, 0x00}, // X
{0x00, 0x44, 0x28, 0x10, 0x10, 0x10, 0x10, 0x00}, // Y
{0x00, 0x7C, 0x08, 0x10, 0x20, 0x40, 0x7C, 0x00}, // Z
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} // Space
};
// Emojis (10 total)
const byte smiley[] PROGMEM = {0x00, 0x66, 0x66, 0x00, 0x81, 0x42, 0x3C, 0x00};
const byte happy[] PROGMEM = {0x00, 0x66, 0x66, 0x00, 0x7E, 0x42, 0x3C, 0x00};
const byte curious[] PROGMEM = {0x00, 0x30, 0x66, 0x00, 0x81, 0x42, 0x3C, 0x00};
const byte surprised[] PROGMEM = {0x00, 0x66, 0x66, 0x00, 0x3C, 0x42, 0x3C, 0x00};
const byte wink[] PROGMEM = {0x00, 0x30, 0x66, 0x00, 0x81, 0x42, 0x3C, 0x00};
const byte excited[] PROGMEM = {0x00, 0x66, 0x66, 0x18, 0x7E, 0x42, 0x3C, 0x00};
const byte confused[] PROGMEM = {0x00, 0x30, 0x66, 0x00, 0x5A, 0x42, 0x24, 0x00};
const byte bored[] PROGMEM = {0x00, 0x60, 0x60, 0x00, 0xFF, 0x00, 0x00, 0x00};
const byte cheeky[] PROGMEM = {0x00, 0x66, 0x06, 0x00, 0x81, 0x42, 0x3C, 0x00};
const byte cool[] PROGMEM = {0x00, 0x66, 0x66, 0x00, 0x7E, 0x24, 0x18, 0x00};
// Messages (80 total)
const char string_0[] PROGMEM = "HI";
const char string_1[] PROGMEM = "I AM R";
const char string_2[] PROGMEM = "WHAZ UP";
const char string_3[] PROGMEM = "HOW ARE YOU";
const char string_4[] PROGMEM = "YO DUDE";
const char string_5[] PROGMEM = "SUP BRO";
const char string_6[] PROGMEM = "CHILLIN";
const char string_7[] PROGMEM = "SEE YA";
const char string_8[] PROGMEM = "COOL VIBES";
const char string_9[] PROGMEM = "HANG OUT";
const char string_10[] PROGMEM = "PEACE OUT";
const char string_11[] PROGMEM = "HEY THERE";
const char string_12[] PROGMEM = "WHAT NOW";
const char string_13[] PROGMEM = "LETZ TALK";
const char string_14[] PROGMEM = "YO WHATS GOOD";
const char string_15[] PROGMEM = "HUDAIR MADE ME";
const char string_16[] PROGMEM = "FROM SHEFFIELD";
const char string_17[] PROGMEM = "AMRC ROCKS";
const char string_18[] PROGMEM = "STILL IN BETA";
const char string_19[] PROGMEM = "PAKISTANI VIBES";
const char string_20[] PROGMEM = "OI MATE IM R";
const char string_21[] PROGMEM = "2025 BABY";
const char string_22[] PROGMEM = "R IS HERE";
const char string_23[] PROGMEM = "SHEFFIELD LADS";
const char string_24[] PROGMEM = "AMRC CREATION";
const char string_25[] PROGMEM = "HUDAIR IS BOSS";
const char string_26[] PROGMEM = "IM A BOT";
const char string_27[] PROGMEM = "BEEP BOOP";
const char string_28[] PROGMEM = "DESK BUDDY R";
const char string_29[] PROGMEM = "LOVE FROM PAK";
const char string_30[] PROGMEM = "R CHILLS";
const char string_31[] PROGMEM = "HUDI MY DUDE";
const char string_32[] PROGMEM = "PAK POWER";
const char string_33[] PROGMEM = "SHEFF VIBES";
const char string_34[] PROGMEM = "AMRC 2025";
const char string_35[] PROGMEM = "R IN BETA";
const char string_36[] PROGMEM = "YO HUDAIR";
const char string_37[] PROGMEM = "BOT LIFE";
const char string_38[] PROGMEM = "BEEP BEEP";
const char string_39[] PROGMEM = "R ROCKS";
const char string_40[] PROGMEM = "PAKISTAN ZINDABAD";
const char string_41[] PROGMEM = "SHEFFIELD LOVE";
const char string_42[] PROGMEM = "AMRC MATE";
const char string_43[] PROGMEM = "BETA BOT";
const char string_44[] PROGMEM = "HUDI ROCKS";
const char string_45[] PROGMEM = "R IS COOL";
const char string_46[] PROGMEM = "2025 RULZ";
const char string_47[] PROGMEM = "DESK PAL";
const char string_48[] PROGMEM = "OI OI R";
const char string_49[] PROGMEM = "PAK PRIDE";
const char string_50[] PROGMEM = "SHEFF BOT";
const char string_51[] PROGMEM = "AMRC STAR";
const char string_52[] PROGMEM = "R SAYS HI";
const char string_53[] PROGMEM = "BETA VIBES";
const char string_54[] PROGMEM = "HUDI BOSS";
const char string_55[] PROGMEM = "BOT BUDDY";
const char string_56[] PROGMEM = "R SEES YOU";
const char string_57[] PROGMEM = "SHEFF 2025";
const char string_58[] PROGMEM = "AMRC COOL";
const char string_59[] PROGMEM = "R IS FUN";
const char string_60[] PROGMEM = "R CHILLIN";
const char string_61[] PROGMEM = "HUDI VIBES";
const char string_62[] PROGMEM = "PAK BOT";
const char string_63[] PROGMEM = "SHEFF STAR";
const char string_64[] PROGMEM = "AMRC BUD";
const char string_65[] PROGMEM = "R IN 2025";
const char string_66[] PROGMEM = "BETA PAL";
const char string_67[] PROGMEM = "HUDI STAR";
const char string_68[] PROGMEM = "BOT VIBES";
const char string_69[] PROGMEM = "R IS READY";
const char string_70[] PROGMEM = "SHEFF RULZ";
const char string_71[] PROGMEM = "AMRC LOVE";
const char string_72[] PROGMEM = "R SAYS YO";
const char string_73[] PROGMEM = "PAK COOL";
const char string_74[] PROGMEM = "HUDI COOL";
const char string_75[] PROGMEM = "R IS BOSS";
const char string_76[] PROGMEM = "AMRC PAL";
const char string_77[] PROGMEM = "SHEFF COOL";
const char string_78[] PROGMEM = "R LOVES PAK";
const char string_79[] PROGMEM = "SO BORED";
const char string_80[] PROGMEM = "WHAT NOW";
const char string_81[] PROGMEM = "NEED ACTION";
const char string_82[] PROGMEM = "OI MATE";
const char string_83[] PROGMEM = "ZZZ SNORE";
const char string_84[] PROGMEM = "WAKE ME R";
const char string_85[] PROGMEM = "SO DULL";
const char string_86[] PROGMEM = "BORED BOT";
const char string_87[] PROGMEM = "R YAWNS";
const char string_88[] PROGMEM = "NEED FUN";
const char string_89[] PROGMEM = "SNOOZE TIME";
const char string_90[] PROGMEM = "R IS SLEEPY";
const char string_91[] PROGMEM = "WHAT TO DO";
const char string_92[] PROGMEM = "Z Z Z";
const char string_93[] PROGMEM = "R NAPS";
const char string_94[] PROGMEM = "TOO QUIET";
const char string_95[] PROGMEM = "WAKE UP MATE";
const char string_96[] PROGMEM = "BORED R";
const char string_97[] PROGMEM = "R SNOOZES";
const char string_98[] PROGMEM = "GIMME ACTION";
const char string_99[] PROGMEM = "SLEEPY BOT";
const char string_100[] PROGMEM = "R IS OUT";
const char string_101[] PROGMEM = "NO FUN HERE";
const char string_102[] PROGMEM = "R ZZZ";
const char string_103[] PROGMEM = "SNORE BOT";
const char string_104[] PROGMEM = "R IS BORED";
const char string_105[] PROGMEM = "NEED VIBES";
const char string_106[] PROGMEM = "SLEEPY R";
const char string_107[] PROGMEM = "R WANTS FUN";
const char string_108[] PROGMEM = "ZZZ BOT";
const char string_109[] PROGMEM = "R IS ZZZ";
const char string_110[] PROGMEM = "BORED 2025";
const char string_111[] PROGMEM = "R NEEDS YOU";
const char string_112[] PROGMEM = "SNOOZE BOT";
const char string_113[] PROGMEM = "R SAYS ZZZ";
const char string_114[] PROGMEM = "NO ACTION";
const char string_115[] PROGMEM = "R IS DULL";
const char string_116[] PROGMEM = "SLEEPY 2025";
const char string_117[] PROGMEM = "R WANTS AMRC";
const char string_118[] PROGMEM = "BORED PAL";
const char string_119[] PROGMEM = "R NEEDS PAK";
const char string_120[] PROGMEM = "SNORE 2025";
const char string_121[] PROGMEM = "R IS SNOOZING";
const char string_122[] PROGMEM = "NO VIBES";
const char string_123[] PROGMEM = "R WANTS SHEFF";
const char string_124[] PROGMEM = "BORED BETA";
const char string_125[] PROGMEM = "R NEEDS HUDI";
const char string_126[] PROGMEM = "SLEEPY PAL";
const char string_127[] PROGMEM = "R WANTS ACTION";
const char string_128[] PROGMEM = "ZZZ 2025";
const char string_129[] PROGMEM = "R IS NAPPING";
const char string_130[] PROGMEM = "BORED AMRC";
const char string_131[] PROGMEM = "R NEEDS VIBES";
const char string_132[] PROGMEM = "SNOOZE PAL";
const char* const messages_table[] PROGMEM = {
string_0, string_1, string_2, string_3, string_4, string_5, string_6, string_7, string_8, string_9, string_10,
string_11, string_12, string_13, string_14, string_15, string_16, string_17, string_18, string_19, string_20,
string_21, string_22, string_23, string_24, string_25, string_26, string_27, string_28, string_29, string_30,
string_31, string_32, string_33, string_34, string_35, string_36, string_37, string_38, string_39, string_40,
string_41, string_42, string_43, string_44, string_45, string_46, string_47, string_48, string_49, string_50,
string_51, string_52, string_53, string_54, string_55, string_56, string_57, string_58, string_59, string_60,
string_61, string_62, string_63, string_64, string_65, string_66, string_67, string_68, string_69, string_70,
string_71, string_72, string_73, string_74, string_75, string_76, string_77, string_78, string_79, string_80,
string_81, string_82, string_83, string_84, string_85, string_86, string_87, string_88, string_89, string_90,
string_91, string_92, string_93, string_94, string_95, string_96, string_97, string_98, string_99, string_100,
string_101, string_102, string_103, string_104, string_105, string_106, string_107, string_108, string_109, string_110,
string_111, string_112, string_113, string_114, string_115, string_116, string_117, string_118, string_119, string_120,
string_121, string_122, string_123, string_124, string_125, string_126, string_127, string_128, string_129, string_130,
string_131, string_132
};
// Sound definitions (8 total)
const int greetingMelody[] PROGMEM = {1000, 1200};
const int greetingDurations[] PROGMEM = {200, 200};
const int happyMelody[] PROGMEM = {1000, 1200, 1400};
const int happyDurations[] PROGMEM = {150, 150, 150};
const int curiousMelody[] PROGMEM = {600, 800};
const int curiousDurations[] PROGMEM = {100, 100};
const int excitedMelody[] PROGMEM = {1200, 1400, 1600};
const int excitedDurations[] PROGMEM = {100, 100, 100};
const int winkMelody[] PROGMEM = {900, 1100};
const int winkDurations[] PROGMEM = {150, 150};
const int chuckleMelody[] PROGMEM = {600, 700, 600};
const int chuckleDurations[] PROGMEM = {100, 100, 100};
const int alertMelody[] PROGMEM = {800, 1000, 800};
const int alertDurations[] PROGMEM = {100, 100, 100};
const int boredMelody[] PROGMEM = {400, 300};
const int boredDurations[] PROGMEM = {300, 300};
// Function prototypes for movement functions
void waveLeftArm();
void nodHead();
void shakeHead();
void doubleWave();
void headSpin();
void armWiggle();
void headTiltLeft();
void headTiltRight();
void headTiltRandom();
void headDroop();
void headWobble();
void armTwitch();
// Reaction struct
struct Reaction {
const char* message;
const byte* emoji;
void (*movement)();
const int* melody;
const int* durations;
int length;
};
// Reactions (48 detection + 58 idle + 54 bored = 160 total)
const Reaction reactions[] PROGMEM = {
// Close (<10 cm): Quick (0-2s)
{string_0, curious, headTiltLeft, curiousMelody, curiousDurations, 2},
{string_1, surprised, shakeHead, alertMelody, alertDurations, 3},
{string_2, wink, nodHead, winkMelody, winkDurations, 2},
{string_3, excited, armWiggle, excitedMelody, excitedDurations, 3},
// Close: Medium (2-5s)
{string_4, curious, waveLeftArm, curiousMelody, curiousDurations, 2},
{string_5, cheeky, doubleWave, chuckleMelody, chuckleDurations, 3},
{string_6, happy, headSpin, happyMelody, happyDurations, 3},
{string_7, cool, headTiltRight, alertMelody, alertDurations, 3},
// Close: Long (5-10s)
{string_8, smiley, waveLeftArm, greetingMelody, greetingDurations, 2},
{string_9, happy, doubleWave, happyMelody, happyDurations, 3},
{string_10, excited, armWiggle, excitedMelody, excitedDurations, 3},
{string_11, cool, headSpin, alertMelody, alertDurations, 3},
// Close: Extra (>10s)
{string_12, excited, doubleWave, excitedMelody, excitedDurations, 3},
{string_13, cheeky, headTiltLeft, chuckleMelody, chuckleDurations, 3},
{string_14, smiley, nodHead, greetingMelody, greetingDurations, 2},
{string_15, cool, headSpin, excitedMelody, excitedDurations, 3},
// Mid (10-20 cm): Quick (0-2s)
{string_16, happy, headTiltRight, happyMelody, happyDurations, 3},
{string_17, curious, nodHead, curiousMelody, curiousDurations, 2},
{string_18, wink, headTiltLeft, winkMelody, winkDurations, 2},
{string_19, excited, armWiggle, excitedMelody, excitedDurations, 3},
// Mid: Medium (2-5s)
{string_20, smiley, nodHead, happyMelody, happyDurations, 3},
{string_21, excited, waveLeftArm, excitedMelody, excitedDurations, 3},
{string_22, cool, headSpin, alertMelody, alertDurations, 3},
{string_23, happy, doubleWave, happyMelody, happyDurations, 3},
// Mid: Long (5-10s)
{string_24, happy, waveLeftArm, happyMelody, happyDurations, 3},
{string_25, smiley, nodHead, greetingMelody, greetingDurations, 2},
{string_26, excited, armWiggle, excitedMelody, excitedDurations, 3},
{string_27, curious, headTiltRight, curiousMelody, curiousDurations, 2},
// Mid: Extra (>10s)
{string_28, wink, nodHead, winkMelody, winkDurations, 2},
{string_29, cool, headSpin, excitedMelody, excitedDurations, 3},
{string_30, happy, doubleWave, happyMelody, happyDurations, 3},
{string_31, cheeky, headTiltLeft, chuckleMelody, chuckleDurations, 3},
// Far (20-30 cm): Quick (0-2s)
{string_32, smiley, waveLeftArm, greetingMelody, greetingDurations, 2},
{string_33, curious, headTiltRight, curiousMelody, curiousDurations, 2},
{string_34, excited, armWiggle, excitedMelody, excitedDurations, 3},
{string_35, happy, nodHead, happyMelody, happyDurations, 3},
// Far: Medium (2-5s)
{string_36, happy, nodHead, happyMelody, happyDurations, 3},
{string_37, cool, waveLeftArm, excitedMelody, excitedDurations, 3},
{string_38, wink, headTiltLeft, winkMelody, winkDurations, 2},
{string_39, smiley, doubleWave, happyMelody, happyDurations, 3},
// Far: Long (5-10s)
{string_40, excited, doubleWave, excitedMelody, excitedDurations, 3},
{string_41, wink, headSpin, winkMelody, winkDurations, 2},
{string_42, happy, waveLeftArm, happyMelody, happyDurations, 3},
{string_43, smiley, nodHead, greetingMelody, greetingDurations, 2},
// Far: Extra (>10s)
{string_44, excited, doubleWave, excitedMelody, excitedDurations, 3},
{string_45, happy, waveLeftArm, happyMelody, happyDurations, 3},
{string_46, cheeky, headTiltRight, chuckleMelody, chuckleDurations, 3},
{string_47, cool, headSpin, excitedMelody, excitedDurations, 3},
// Idle (no detection, >5s, 58 total)
{string_48, cool, headTiltRandom, chuckleMelody, chuckleDurations, 3},
{string_49, smiley, armWiggle, happyMelody, happyDurations, 3},
{string_50, happy, doubleWave, happyMelody, happyDurations, 3},
{string_51, smiley, nodHead, greetingMelody, greetingDurations, 2},
{string_52, excited, waveLeftArm, excitedMelody, excitedDurations, 3},
{string_53, curious, headTiltLeft, curiousMelody, curiousDurations, 2},
{string_54, cheeky, headSpin, chuckleMelody, chuckleDurations, 3},
{string_55, cool, armWiggle, excitedMelody, excitedDurations, 3},
{string_56, happy, nodHead, happyMelody, happyDurations, 3},
{string_57, smiley, waveLeftArm, greetingMelody, greetingDurations, 2},
{string_58, excited, doubleWave, excitedMelody, excitedDurations, 3},
{string_59, cool, headSpin, excitedMelody, excitedDurations, 3},
{string_60, happy, armWiggle, happyMelody, happyDurations, 3},
{string_61, curious, headTiltLeft, curiousMelody, curiousDurations, 2},
{string_62, smiley, nodHead, greetingMelody, greetingDurations, 2},
{string_63, cheeky, headWobble, chuckleMelody, chuckleDurations, 3},
{string_64, cool, armTwitch, excitedMelody, excitedDurations, 3},
{string_65, excited, doubleWave, happyMelody, happyDurations, 3},
{string_66, happy, waveLeftArm, happyMelody, happyDurations, 3},
{string_67, smiley, headSpin, greetingMelody, greetingDurations, 2},
{string_68, cool, armWiggle, excitedMelody, excitedDurations, 3},
{string_69, curious, headTiltRight, curiousMelody, curiousDurations, 2},
{string_70, happy, nodHead, happyMelody, happyDurations, 3},
{string_71, cool, headWobble, excitedMelody, excitedDurations, 3},
{string_72, excited, doubleWave, excitedMelody, excitedDurations, 3},
{string_73, smiley, waveLeftArm, greetingMelody, greetingDurations, 2},
{string_74, cheeky, headSpin, chuckleMelody, chuckleDurations, 3},
{string_75, happy, armTwitch, happyMelody, happyDurations, 3},
{string_76, cool, headTiltLeft, excitedMelody, excitedDurations, 3},
{string_77, excited, doubleWave, happyMelody, happyDurations, 3},
{string_78, smiley, nodHead, greetingMelody, greetingDurations, 2},
{string_79, curious, headWobble, curiousMelody, curiousDurations, 2},
{string_80, happy, waveLeftArm, happyMelody, happyDurations, 3},
{string_81, cheeky, armTwitch, chuckleMelody, chuckleDurations, 3},
{string_82, cool, headTiltRight, excitedMelody, excitedDurations, 3},
{string_83, happy, doubleWave, happyMelody, happyDurations, 3},
{string_84, excited, headSpin, excitedMelody, excitedDurations, 3},
{string_85, smiley, nodHead, greetingMelody, greetingDurations, 2},
{string_86, happy, armWiggle, happyMelody, happyDurations, 3},
{string_87, cool, headWobble, excitedMelody, excitedDurations, 3},
{string_88, cheeky, armTwitch, chuckleMelody, chuckleDurations, 3},
{string_89, happy, waveLeftArm, happyMelody, happyDurations, 3},
{string_90, excited, doubleWave, excitedMelody, excitedDurations, 3},
{string_91, cool, headSpin, excitedMelody, excitedDurations, 3},
{string_92, curious, headTiltLeft, curiousMelody, curiousDurations, 2},
{string_93, smiley, nodHead, greetingMelody, greetingDurations, 2},
{string_94, cheeky, armWiggle, chuckleMelody, chuckleDurations, 3},
{string_95, happy, waveLeftArm, happyMelody, happyDurations, 3},
{string_96, cool, headWobble, excitedMelody, excitedDurations, 3},
{string_97, excited, doubleWave, excitedMelody, excitedDurations, 3},
{string_98, smiley, nodHead, greetingMelody, greetingDurations, 2},
{string_99, happy, armTwitch, happyMelody, happyDurations, 3},
{string_100, cool, headSpin, excitedMelody, excitedDurations, 3},
{string_101, excited, waveLeftArm, excitedMelody, excitedDurations, 3},
{string_102, happy, headTiltRight, happyMelody, happyDurations, 3},
{string_103, smiley, armWiggle, greetingMelody, greetingDurations, 2},
{string_104, cheeky, headWobble, chuckleMelody, chuckleDurations, 3},
// Bored (no detection, >60s, 54 total)
{string_105, bored, headDroop, boredMelody, boredDurations, 2},
{string_106, confused, shakeHead, curiousMelody, curiousDurations, 2},
{string_107, curious, headTiltRight, curiousMelody, curiousDurations, 2},
{string_108, cheeky, headSpin, chuckleMelody, chuckleDurations, 3},
{string_109, bored, headDroop, boredMelody, boredDurations, 2},
{string_110, confused, headTiltLeft, curiousMelody, curiousDurations, 2},
{string_111, bored, headWobble, boredMelody, boredDurations, 2},
{string_112, curious, shakeHead, curiousMelody, curiousDurations, 2},
{string_113, bored, headDroop, boredMelody, boredDurations, 2},
{string_114, confused, headTiltRight, curiousMelody, curiousDurations, 2},
{string_115, bored, headWobble, boredMelody, boredDurations, 2},
{string_116, curious, headDroop, curiousMelody, curiousDurations, 2},
{string_117, confused, shakeHead, curiousMelody, curiousDurations, 2},
{string_118, bored, headDroop, boredMelody, boredDurations, 2},
{string_119, bored, headWobble, boredMelody, boredDurations, 2},
{string_120, confused, headTiltLeft, curiousMelody, curiousDurations, 2},
{string_121, curious, shakeHead, curiousMelody, curiousDurations, 2},
{string_122, bored, headDroop, boredMelody, boredDurations, 2},
{string_123, bored, headWobble, boredMelody, boredDurations, 2},
{string_124, confused, headTiltRight, curiousMelody, curiousDurations, 2},
{string_125, bored, headDroop, boredMelody, boredDurations, 2},
{string_126, curious, shakeHead, curiousMelody, curiousDurations, 2},
{string_127, bored, headWobble, boredMelody, boredDurations, 2},
{string_128, bored, headDroop, boredMelody, boredDurations, 2},
{string_129, confused, headTiltLeft, curiousMelody, curiousDurations, 2},
{string_130, bored, headWobble, boredMelody, boredDurations, 2},
{string_131, curious, shakeHead, curiousMelody, curiousDurations, 2},
{string_132, bored, headDroop, boredMelody, boredDurations, 2},
};
// State and timing
bool isDetecting = false;
unsigned long detectionStartTime = 0;
unsigned long lastActionTime = 0;
void setup() {
// Initialize components
lc.shutdown(0, false);
lc.setIntensity(0, 8);
lc.clearDisplay(0);
leftArmStepper.setSpeed(10);
rightArmServo.attach(SERVO_RIGHT_ARM);
headServo.attach(SERVO_HEAD);
moveRightArm(90);
moveHead(90);
pinMode(BUZZER, OUTPUT);
pinMode(TRIG, OUTPUT);
pinMode(ECHO, INPUT);
// Debug: Display static 'R' for 3 seconds
for (int i = 0; i < 8; i++) {
lc.setRow(0, i, pgm_read_byte(&font[17][i]));
}
delay(3000);
lc.clearDisplay(0);
// Debug: Display "HI" one character at a time
displayChar('H');
delay(500);
displayChar('I');
delay(500);
displayFace(smiley);
delay(1000);
lc.clearDisplay(0);
// Startup: Display "I AM R"
scrollMessage_P(string_1, happy);
playTune_P(greetingMelody, greetingDurations, 2);
}
void loop() {
long distance = getDistance();
if (distance < 30 && distance > 0) {
if (!isDetecting) {
isDetecting = true;
detectionStartTime = millis();
}
unsigned long duration = millis() - detectionStartTime;
int reactionIdx = -1;
if (distance < 10) {
if (duration < 2000) reactionIdx = random(0, 4); // Close: Quick
else if (duration < 5000) reactionIdx = random(4, 8); // Close: Medium
else if (duration < 10000) reactionIdx = random(8, 12); // Close: Long
else reactionIdx = random(12, 16); // Close: Extra
} else if (distance < 20) {
if (duration < 2000) reactionIdx = random(16, 20); // Mid: Quick
else if (duration < 5000) reactionIdx = random(20, 24); // Mid: Medium
else if (duration < 10000) reactionIdx = random(24, 28); // Mid: Long
else reactionIdx = random(28, 32); // Mid: Extra
} else {
if (duration < 2000) reactionIdx = random(32, 36); // Far: Quick
else if (duration < 5000) reactionIdx = random(36, 40); // Far: Medium
else if (duration < 10000) reactionIdx = random(40, 44); // Far: Long
else reactionIdx = random(44, 48); // Far: Extra
}
if (reactionIdx >= 0) {
performReaction_P(reactionIdx);
lastActionTime = millis();
}
} else {
if (isDetecting) {
isDetecting = false;
lastActionTime = millis();
}
unsigned long idleTime = millis() - lastActionTime;
if (idleTime > 5000 && millis() - lastActionTime > random(2000, 5000)) {
performReaction_P(random(48, 106)); // Idle (58 reactions)
lastActionTime = millis();
} else if (idleTime > 60000 && millis() - lastActionTime > random(20000, 30000)) {
performReaction_P(random(106, 160)); // Bored (54 reactions)
lastActionTime = millis();
}
}
}
// --- Helper Functions ---
long getDistance() {
digitalWrite(TRIG, LOW);
delayMicroseconds(2);
digitalWrite(TRIG, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG, LOW);
long duration = pulseIn(ECHO, HIGH, 30000);
return (duration == 0) ? -1 : duration / 58;
}
byte reverseBits(byte b) {
byte r = 0;
for (int i = 0; i < 8; i++) {
r |= ((b >> i) & 0x01) << (7 - i);
}
return r;
}
void displayFace(const byte face[]) {
for (int i = 0; i < 8; i++) {
lc.setRow(0, i, pgm_read_byte(&face[i]));
}
}
void displayChar(char c) {
int charIdx = (c == ' ') ? 26 : c - 'A';
if (charIdx >= 0 && charIdx < 27) {
for (int i = 0; i < 8; i++) {
lc.setRow(0, i, pgm_read_byte(&font[charIdx][i]));
}
}
delay(500);
}
void scrollMessage_P(const char* message, const byte* emoji) {
char buffer[32]; // Temporary buffer for string from PROGMEM
strcpy_P(buffer, message);
int len = strlen(buffer);
for (int c = 0; c < len; c++) {
displayChar(buffer[c]);
}
displayFace(emoji);
delay(1000);
lc.clearDisplay(0);
}
void moveHead(int angle) {
headServo.write(constrain(angle, 0, 180));
}
void moveRightArm(int angle) {
rightArmServo.write(constrain(angle, 0, 180));
}
void waveLeftArm() {
for (int i = 0; i < 3; i++) {
leftArmStepper.step(256);
delay(200);
leftArmStepper.step(-256);
delay(200);
}
}
void nodHead() {
for (int i = 0; i < 2; i++) {
moveHead(random(95, 105));
delay(200);
moveHead(random(75, 85));
delay(200);
}
moveHead(90);
}
void shakeHead() {
for (int i = 0; i < 3; i++) {
moveHead(random(100, 120));
delay(150);
moveHead(random(60, 80));
delay(150);
}
moveHead(90);
}
void doubleWave() {
for (int i = 0; i < 2; i++) {
moveRightArm(random(110, 130));
leftArmStepper.step(256);
delay(200);
moveRightArm(90);
leftArmStepper.step(-256);
delay(200);
}
}
void headSpin() {
moveHead(random(40, 60));
delay(150);
moveHead(random(120, 140));
delay(150);
moveHead(90);
}
void armWiggle() {
for (int i = 0; i < 3; i++) {
moveRightArm(random(90, 110));
delay(100);
moveRightArm(random(70, 90));
delay(100);
}
moveRightArm(90);
}
void headTiltLeft() {
moveHead(random(60, 80));
delay(1000);
moveHead(90);
}
void headTiltRight() {
moveHead(random(100, 120));
delay(1000);
moveHead(90);
}
void headTiltRandom() {
moveHead(random(60, 120));
delay(1000);
moveHead(90);
}
void headDroop() {
moveHead(random(110, 130));
delay(1000);
moveHead(90);
}
void headWobble() {
for (int i = 0; i < 3; i++) {
moveHead(random(80, 100));
delay(100);
moveHead(random(80, 100));
delay(100);
}
moveHead(90);
}
void armTwitch() {
for (int i = 0; i < 2; i++) {
moveRightArm(random(100, 120));
delay(80);
moveRightArm(random(80, 100));
delay(80);
}
moveRightArm(90);
}
void playTune_P(const int melody[], const int durations[], int length) {
for (int i = 0; i < length; i++) {
tone(BUZZER, pgm_read_word(&melody[i]), pgm_read_word(&durations[i]));
delay(pgm_read_word(&durations[i]) * 1.3);
noTone(BUZZER);
}
}
void performReaction_P(int index) {
Reaction reaction;
memcpy_P(&reaction, &reactions[index], sizeof(Reaction));
scrollMessage_P(reaction.message, reaction.emoji);
reaction.movement();
playTune_P(reaction.melody, reaction.durations, reaction.length);
}