#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
#define MIC_SCK D7
#define MIC_WS D8
#define MIC_SD D9
#define AMP_BCLK D1
#define AMP_LRC D0
#define AMP_DIN D6
#define BUZZER D2
void faceNeutral() {
display.clearDisplay();
display.fillCircle(35, 25, 10, WHITE); display.fillCircle(35, 25, 5, BLACK);
display.fillCircle(93, 25, 10, WHITE); display.fillCircle(93, 25, 5, BLACK);
display.drawLine(44, 50, 84, 50, WHITE);
display.display();
}
void faceHappy() {
display.clearDisplay();
display.fillCircle(35, 25, 10, WHITE); display.fillCircle(35, 25, 5, BLACK);
display.fillCircle(93, 25, 10, WHITE); display.fillCircle(93, 25, 5, BLACK);
for (int x = -20; x <= 20; x++) {
int y = (x * x) / 20;
display.drawPixel(64 + x, 46 + y, WHITE);
display.drawPixel(64 + x, 47 + y, WHITE);
}
display.display();
}
void faceSad() {
display.clearDisplay();
display.fillCircle(35, 25, 10, WHITE); display.fillCircle(35, 27, 5, BLACK);
display.fillCircle(93, 25, 10, WHITE); display.fillCircle(93, 27, 5, BLACK);
for (int x = -20; x <= 20; x++) {
int y = -(x * x) / 20;
display.drawPixel(64 + x, 55 + y, WHITE);
display.drawPixel(64 + x, 56 + y, WHITE);
}
display.display();
}
void faceSurprised() {
display.clearDisplay();
display.fillCircle(35, 25, 12, WHITE); display.fillCircle(35, 25, 6, BLACK);
display.fillCircle(93, 25, 12, WHITE); display.fillCircle(93, 25, 6, BLACK);
display.drawCircle(64, 52, 8, WHITE);
display.display();
}
void faceThinking() {
display.clearDisplay();
display.fillCircle(35, 25, 10, WHITE); display.fillCircle(35, 25, 5, BLACK);
display.fillRoundRect(83, 22, 20, 7, 3, WHITE);
for (int x = 0; x < 8; x++)
display.fillRect(44 + x*5, 50 + (x%2)*4, 4, 2, WHITE);
display.setTextSize(1); display.setCursor(100, 5); display.print("?");
display.display();
}
void faceAngry() {
display.clearDisplay();
display.drawLine(25, 15, 45, 22, WHITE);
display.drawLine(83, 22, 103, 15, WHITE);
display.fillCircle(35, 28, 9, WHITE); display.fillCircle(35, 28, 4, BLACK);
display.fillCircle(93, 28, 9, WHITE); display.fillCircle(93, 28, 4, BLACK);
display.drawLine(44, 54, 84, 54, WHITE);
display.drawLine(44, 54, 48, 58, WHITE);
display.drawLine(84, 54, 80, 58, WHITE);
display.display();
}
void faceCool() {
display.clearDisplay();
display.fillRoundRect(20, 18, 30, 16, 4, WHITE);
display.fillRoundRect(22, 20, 26, 12, 3, BLACK);
display.fillRoundRect(78, 18, 30, 16, 4, WHITE);
display.fillRoundRect(80, 20, 26, 12, 3, BLACK);
display.drawLine(50, 24, 78, 24, WHITE);
display.drawLine(44, 50, 70, 48, WHITE);
display.drawLine(70, 48, 84, 52, WHITE);
display.display();
}
void faceTalking(int frame) {
display.clearDisplay();
display.fillCircle(35, 25, 10, WHITE); display.fillCircle(35, 25, 5, BLACK);
display.fillCircle(93, 25, 10, WHITE); display.fillCircle(93, 25, 5, BLACK);
if (frame % 2 == 0) {
display.fillRoundRect(44, 46, 40, 14, 6, WHITE);
display.fillRoundRect(48, 50, 32, 6, 4, BLACK);
} else {
display.drawLine(44, 52, 84, 52, WHITE);
}
display.display();
}
void showText(String line1, String line2 = "", String line3 = "") {
display.clearDisplay();
display.fillCircle(35, 20, 8, WHITE); display.fillCircle(35, 20, 4, BLACK);
display.fillCircle(93, 20, 8, WHITE); display.fillCircle(93, 20, 4, BLACK);
display.drawLine(44, 35, 84, 35, WHITE);
display.setTextSize(1);
display.setCursor(0, 40); display.print(line1);
if (line2 != "") { display.setCursor(0, 50); display.print(line2); }
if (line3 != "") { display.setCursor(0, 58); display.print(line3); }
display.display();
}
void speakWord(String word, int frame) {
faceTalking(frame);
int freq = constrain(300 + ((word.charAt(0)-'a')*30) + (word.length()*20), 200, 900);
int dur = 80 + word.length() * 20;
tone(BUZZER, freq, dur);
delay(dur + 40);
noTone(BUZZER);
delay(40);
}
void robotSay(String sentence, void (*faceFn)()) {
Serial.printf("[ROBOT]: %s\n", sentence.c_str());
String lower = sentence; lower.toLowerCase();
String word = ""; int frame = 0;
for (int i = 0; i <= lower.length(); i++) {
char c = lower.charAt(i);
if (c == ' ' || i == lower.length()) {
if (word.length() > 0) {
speakWord(word, frame++);
if (faceFn) faceFn();
word = "";
}
} else word += c;
}
}
// 67 jokes - setup and punchline
const char* jokeSetup[67] = {
"Why did the ESP32 cross the road?",
"Why do programmers prefer dark mode?",
"What do you call a robot that loves math?",
"Why was the robot good at soccer?",
"What do robots eat for breakfast?",
"Why did the computer go to the doctor?",
"What do you call a sleeping dinosaur?",
"Why dont scientists trust atoms?",
"What do you call cheese that isnt yours?",
"Why did the scarecrow win an award?",
"What do you call a fish without eyes?",
"Why cant you give Elsa a balloon?",
"What do you call a bear with no teeth?",
"Why did the bicycle fall over?",
"What do elves learn in school?",
"Why did the math book look so sad?",
"What do you call a fake noodle?",
"Why did the golfer bring extra pants?",
"What do you call a dinosaur that crashes computers?",
"Why did the robot go to school?",
"What do you call a lazy kangaroo?",
"Why did the banana go to the doctor?",
"What do you call a sleeping bull?",
"Why did the cookie go to the hospital?",
"What do computers eat for snacks?",
"Why did the programmer quit his job?",
"What do you call a fish that wears a crown?",
"Why dont eggs tell jokes?",
"What do you call a belt made of watches?",
"Why did the robot blush?",
"What do you call a snowman with a six pack?",
"Why did the calendar go to therapy?",
"What do you call a pile of cats?",
"Why did the tomato turn red?",
"What do you call an alligator in a vest?",
"Why did the frog take the bus?",
"What do you call a sad strawberry?",
"Why did the invisible man turn down the job?",
"What do you call a dog magician?",
"Why did the coffee file a police report?",
"What do you call a pig that does karate?",
"Why did the teddy bear say no to dessert?",
"What do you call a cow on a trampoline?",
"Why did the student eat his homework?",
"What do you call a lazy pebble?",
"Why did the robot get angry?",
"What do you call a fish that plays guitar?",
"Why did the moon break up with the sun?",
"What do you call a broken boomerang?",
"Why did the skeleton go to the party alone?",
"What do you call a penguin in the desert?",
"Why did the computer keep sneezing?",
"What do you call a smart phone that sings?",
"Why did the robot fail the driving test?",
"What do you call a baby computer?",
"Why did the wifi router go to therapy?",
"What do you call a dog with a fever?",
"Why dont programmers like nature?",
"What do you call an angry LED?",
"Why did the circuit go to school?",
"What do you call a robot chef?",
"Why did the robot go to the gym?",
"What do you call a nervous robot?",
"Why did the Arduino break up with the Raspberry Pi?",
"What do you call a robot that always lies?",
"What do you call an ESP32 in space?",
"Why did the LED turn off?"
};
const char* jokePunchline[67] = {
"To get to the other WiFi!",
"Because light attracts bugs!",
"An algo rhythm!",
"It had great byte control!",
"Micro chips and RAM!",
"It had a virus!",
"A dino snore!",
"They make up everything!",
"Nacho cheese!",
"He was outstanding in his field!",
"A fsh! The F is silent!",
"She will let it go!",
"A gummy bear!",
"It was two tired!",
"The elf abet!",
"It had too many problems!",
"An impasta!",
"In case he got a hole in one!",
"A tyrannosaurus rex!",
"To improve its byte size!",
"A pouch potato!",
"It wasnt peeling well!",
"A bulldozer!",
"It was feeling crummy!",
"Micro chips!",
"He did not get arrays!",
"King of the sea!",
"They would crack each other up!",
"A waist of time!",
"It saw the motherboard!",
"An abdominal snowman!",
"It had too many dates!",
"A meowtain!",
"It saw the salad dressing!",
"An investigator!",
"Its car was toad!",
"A blueberry!",
"He could not see himself doing it!",
"A labracadabrador!",
"It got mugged!",
"A pork chop!",
"She was already stuffed!",
"A milkshake!",
"The teacher said it was a piece of cake!",
"A little boulder!",
"Someone pushed its buttons!",
"A bass player!",
"It needed some space!",
"Just a stick!",
"He had no body to go with!",
"Lost!",
"It had a bad cache!",
"An iPhone!",
"It kept making U turns!",
"A motherboard!",
"It had too many connections!",
"A hot dog!",
"Too many bugs no features!",
"A glowing mad pixel!",
"To improve its connections!",
"A byte master!",
"To work on its hardware!",
"A jitter bug!",
"It needed more memory to commit!",
"A liar liar circuits on fire!",
"An astro-not!",
"It ran out of charge!"
};
void playStartup() {
int n[] = {262, 330, 392, 523};
for (int i = 0; i < 4; i++) { tone(BUZZER, n[i], 150); delay(170); }
noTone(BUZZER);
}
void playError() {
tone(BUZZER, 200, 300); delay(350);
tone(BUZZER, 150, 500); delay(550);
noTone(BUZZER);
}
void playWow() {
for (int f = 400; f <= 1000; f += 100) { tone(BUZZER, f, 60); delay(70); }
noTone(BUZZER);
}
void setup() {
Serial.begin(115200);
delay(1000);
Wire.begin(D4, D5);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("ERROR: OLED not found!");
while(1);
}
Serial.println("OLED OK!");
display.display();
delay(1500);
display.setTextColor(WHITE);
display.setTextSize(1);
pinMode(MIC_SCK, OUTPUT); pinMode(MIC_WS, OUTPUT); pinMode(MIC_SD, INPUT);
digitalWrite(MIC_SCK, LOW); digitalWrite(MIC_WS, LOW);
pinMode(AMP_BCLK, OUTPUT); pinMode(AMP_LRC, OUTPUT); pinMode(AMP_DIN, OUTPUT);
digitalWrite(AMP_BCLK, LOW); digitalWrite(AMP_LRC, LOW); digitalWrite(AMP_DIN, LOW);
pinMode(BUZZER, OUTPUT);
faceSurprised(); playStartup();
faceHappy(); delay(800);
faceNeutral();
Serial.println("=====================================");
Serial.println(" AI Desk Buddy Ready! 🤖 ");
Serial.println("=====================================");
Serial.println("hello, bye, joke, sing, weather,");
Serial.println("math, think, time, name, age,");
Serial.println("color, food, angry, cool,");
Serial.println("compliment, fact, help");
Serial.println("=====================================");
}
void loop() {
if (Serial.available()) {
String cmd = Serial.readStringUntil('\n');
cmd.trim(); cmd.toLowerCase();
Serial.printf("\n[YOU]: %s\n", cmd.c_str());
if (cmd.indexOf("hello") >= 0 || cmd.indexOf("hi") >= 0) {
faceHappy();
robotSay("Hello! Great to see you! I am your AI desk buddy!", faceHappy);
} else if (cmd.indexOf("bye") >= 0 || cmd.indexOf("goodbye") >= 0) {
faceSad();
robotSay("Goodbye! Come back soon! I will miss you!", faceSad);
} else if (cmd.indexOf("joke") >= 0) {
faceThinking(); delay(300);
int r = random(0, 67);
faceHappy();
robotSay(String(jokeSetup[r]), faceHappy);
delay(500);
robotSay(String(jokePunchline[r]), faceHappy);
} else if (cmd.indexOf("sing") >= 0) {
faceHappy();
robotSay("La la la here is my song for you!", faceHappy);
int m[] = {262,294,330,349,392,440,494,523,494,440,392,349,330,294,262};
for (int i=0; i<15; i++) { faceTalking(i); tone(BUZZER,m[i],200); delay(220); }
noTone(BUZZER); faceHappy();
Serial.println("[ROBOT]: How was that?!");
} else if (cmd.indexOf("weather") >= 0) {
faceThinking(); delay(500); playWow();
showText("Weather Report:", "Temp: 22C / 72F", "Sunny & Perfect!");
Serial.println("[ROBOT]: Temp: 22C, Sunny and Perfect!");
delay(3000);
faceHappy();
robotSay("It is sunny and 22 degrees! Perfect day!", faceHappy);
} else if (cmd.indexOf("math") >= 0) {
faceThinking();
int plus = cmd.indexOf("+");
int minus = cmd.indexOf("-");
int times = cmd.indexOf("*");
int divid = cmd.indexOf("/");
float a = 0, b = 0, result = 0;
String op = "";
if (plus > 0) { a = cmd.substring(5, plus).toFloat(); b = cmd.substring(plus+1).toFloat(); result = a+b; op="+"; }
else if (minus > 5) { a = cmd.substring(5, minus).toFloat(); b = cmd.substring(minus+1).toFloat(); result = a-b; op="-"; }
else if (times > 0) { a = cmd.substring(5, times).toFloat(); b = cmd.substring(times+1).toFloat(); result = a*b; op="*"; }
else if (divid > 0) { a = cmd.substring(5, divid).toFloat(); b = cmd.substring(divid+1).toFloat(); if(b!=0) { result = a/b; op="/"; } }
if (op != "") {
showText("Math:", String(a,0)+op+String(b,0)+"="+String(result,1), "");
Serial.printf("[ROBOT]: %.0f %s %.0f = %.2f\n", a, op.c_str(), b, result);
playWow(); delay(2000);
faceHappy();
robotSay("The answer is " + String(result, 1), faceHappy);
} else {
faceThinking();
robotSay("Try math 5+3 or math 10*2!", faceThinking);
}
} else if (cmd.indexOf("think") >= 0) {
faceThinking();
robotSay("Hmm let me think about that!", faceThinking);
delay(500); faceSurprised();
robotSay("I have got it! The answer is 42!", faceHappy);
} else if (cmd.indexOf("time") >= 0 || cmd.indexOf("uptime") >= 0) {
faceNeutral();
unsigned long s = millis()/1000;
unsigned long m = s/60; s = s%60;
showText("Uptime:", String(m)+"m "+String(s)+"s", "");
delay(2000); faceNeutral();
robotSay("I have been running for " + String(m) + " minutes!", faceNeutral);
} else if (cmd.indexOf("name") >= 0) {
faceHappy();
robotSay("My name is Buddy! Your AI desk companion!", faceHappy);
} else if (cmd.indexOf("age") >= 0) {
faceHappy();
robotSay("I was born when you plugged me in! Very young!", faceHappy);
} else if (cmd.indexOf("color") >= 0) {
faceCool(); delay(500);
robotSay("My favorite color is electric blue!", faceNeutral);
faceCool(); delay(500);
} else if (cmd.indexOf("food") >= 0) {
faceHappy();
robotSay("I love eating electricity and fresh code!", faceHappy);
} else if (cmd.indexOf("angry") >= 0) {
faceAngry();
tone(BUZZER, 150, 500); delay(550); noTone(BUZZER);
Serial.println("[ROBOT]: GRRR! Just kidding!");
delay(1000); faceHappy();
robotSay("Just kidding! I could never stay angry!", faceHappy);
} else if (cmd.indexOf("cool") >= 0) {
faceCool(); delay(1000);
tone(BUZZER, 800, 200); delay(250); noTone(BUZZER);
robotSay("Yeah I know I am pretty cool! Deal with it!", faceNeutral);
faceCool(); delay(1000);
} else if (cmd.indexOf("compliment") >= 0) {
faceHappy(); playWow();
int r = random(0, 4);
if (r == 0) robotSay("You are absolutely brilliant! The smartest human I know!", faceHappy);
else if (r == 1) robotSay("You have excellent taste! You chose the best robot buddy!", faceHappy);
else if (r == 2) robotSay("You are like a 5 volt supply! You power everything!", faceHappy);
else robotSay("You are the best programmer in the whole world!", faceHappy);
} else if (cmd.indexOf("fact") >= 0) {
faceThinking(); delay(300); playWow();
int r = random(0, 5);
faceSurprised();
if (r == 0) robotSay("Lightning is 5 times hotter than the sun! Wow!", faceSurprised);
else if (r == 1) robotSay("Honey never spoils! 3000 year old honey is edible!", faceSurprised);
else if (r == 2) robotSay("Octopuses have 3 hearts and blue blood! Amazing!", faceSurprised);
else if (r == 3) robotSay("The first computer bug was a real moth in 1947!", faceSurprised);
else robotSay("More stars exist than grains of sand on Earth!", faceSurprised);
} else if (cmd.indexOf("help") >= 0) {
faceNeutral();
Serial.println("[ROBOT]: Commands:");
Serial.println(" hello, bye, joke, sing");
Serial.println(" weather, math 5+3");
Serial.println(" think, time, name, age");
Serial.println(" color, food, angry, cool");
Serial.println(" compliment, fact, help");
tone(BUZZER, 523, 100); delay(150); noTone(BUZZER);
} else {
faceThinking();
playError();
robotSay("I do not understand! Type help for commands!", faceThinking);
}
faceNeutral();
Serial.println("\nType a command...");
}
}
Loading
xiao-esp32-s3
xiao-esp32-s3