Good news everybody. With the help of Professor Farnsworth, I have rehabilitated Robot Santa and brought him back from the future to help us in 2021. His anger limiting module has been updated and his naughty/nice detector recalibrated into a more normal range.
Background:
Robot Santa is a 4-ton robot that was built by Friendly Robot Company in the year 2801. His purpose is to judge whether people are naughty or nice and to deliver presents accordingly. I suspect this was a labor-saving endeavor. Rather than living at the North Pole, he is based on Neptune and flies to Earth on a sled pulled by rocket-powered reindeer. Unfortunately, due to a programming error, his standards are so high that essentially everyone is judged naughty. Worse, he carries out murder and mayhem on anyone he sees on the streets on "Xmas" eve while flying around the Earth.
Credit: Professor Hubert J. Farnsworth as drawn by Matt Groening in the television series Futurama
Thus the reasoning behind the reprogramming effort that Professor Farnsworth and I undertook.
Mechanical Fabrication:
Robot Santa was designed in Fusion 360 by me and fabricated using PLA in a 3D printer. An exploded-view is shown below.
Starting upper-right, the hat consists of a ring, the hat itself, and the mace-like ball that was glued together with epoxy. The two ears were glued to the head with hot glue. This was a mistake as it cooled quickly and didn't allow easy adjustment such that they are slightly out of alignment. The nose, teeth, and eyes were also attached with hot glue. The beard was glued to the head with epoxy. The base features a light diffusing screen behind the teeth and attachment locations for the speaker, microcontroller, and LEDs. The base attaches and detaches easily to the head using a bayonet-style assembly.
Electronics - Arduino Inside
An Arduino Nano 33 IOT provides Artificial Intelligence suitable for an advanced robot of this type with communication over WiFi. Additional capabilities are provided by the following:
- 1 x Speech Unit: JQ6500
- 1 x Speaker: 4cm OD, 8-ohm, 2W
- 6 x WS2812 "Neopixel" LEDs on custom PCBs
- 1 x USB external power
The schematic is given below.
Assembly:
The electronic assembly was tested first on a breadboard as I was not familiar with the Nano 33 IOT.
A prototyping board was then soldered with headers for the microcontroller and JQ6500 sound board. DuPont-style pins crimped to the wires so that they could be easily reassembled inside the robot.
The speaker, LEDs, and microcontroller assembly were then ready for placement.
This is by far the tidiest assembly of the animatronic robots I have made.
Code:
The code is similar to my previous animatronic robots. Rather than listing all commands in the user interface, the associated number for a command is sent over WiFi using the following interface.
I am not particularly proud of the code and didn't make any attempt to perfect it. Further, I am going to assume you don't want me to lead you through poorly written code. But it appears to work and is posted below for those who care to peruse it.
/* Robot Santa V0_4
* developed on Arduino Nano 33 IOT
*
* Controls Robot Santa using a simple webserver
* Lights eyes and mouth with six WS2812 LEDs
* Speech using JQ6500 and JQ6500_Serial library
*
* by fmilburn December 2021
*
* This code is in the public domain
*/
#include <Arduino.h>
#include <JQ6500_Serial.h>
#include "Secrets.h"
#include <WiFiNINA.h>
#include <WiFiUdp.h>
#include <Adafruit_NeoPixel.h>
#include "SpeechData.h"
// WiFi
char ssid[] = SECRET_SSID; // network SSID (name)
char pass[] = SECRET_PASS; // network password
int status = WL_IDLE_STATUS;
WiFiServer server(80); // server socket
WiFiClient client = server.available();
String inputString; // web server input
// Pixels
const int PIXELPIN = 6;
const int NUMPIXELS = 6;
// Miscellaneous
const int DEBUG = false;
int commandNumber; // Command (speech track) to play
// Instantiation
JQ6500_Serial mp3(Serial1);
Adafruit_NeoPixel pixels(NUMPIXELS, PIXELPIN, NEO_GRB + NEO_KHZ800);
// ------------------------------------------------------------------------
// setup
// ------------------------------------------------------------------------
void setup() {
if (DEBUG == true){
Serial.begin(115200);
while(!Serial){
// wait for serial
}
}
// JQ6500 speech
Serial1.begin(9600);
mp3.reset();
mp3.setVolume(VOLUME);
mp3.setLoopMode(MP3_LOOP_NONE);
// WiFi
startWiFi();
// WS2812 / neopixels
pixels.begin();
pixels.clear();
flashEyes(track[0]);
}
// ------------------------------------------------------------------------
// loop
// ------------------------------------------------------------------------
void loop() {
if (WiFi.status() != WL_CONNECTED) {
startWiFi();
}
client = server.available();
if (client){
doWebPage();
if (DEBUG == true){
Serial.print("Inside loop, Track: ");
Serial.println(commandNumber);
Serial.print("Track Length: ");
Serial.println(track[commandNumber]);
}
}
if ((commandNumber >= 0) && (commandNumber < TRACKS)) {
// if currently speaking, let it complete
if (mp3.getStatus() != MP3_STATUS_PLAYING) {
speak(commandNumber);
commandNumber = -1;
}
}
}
// ------------------------------------------------------------------------
// showPixels
// ------------------------------------------------------------------------
void showPixels(int redEye, int greenEye, int blueEye,
int redMouth, int greenMouth, int blueMouth){
pixels.setPixelColor(0, pixels.Color(redMouth, greenMouth, blueMouth));
pixels.setPixelColor(1, pixels.Color(redMouth, greenMouth, blueMouth));
pixels.setPixelColor(2, pixels.Color(redMouth, greenMouth, blueMouth));
pixels.setPixelColor(3, pixels.Color(redMouth, greenMouth, blueMouth));
pixels.setPixelColor(4, pixels.Color(redEye, greenEye, blueEye));
pixels.setPixelColor(5, pixels.Color(redEye, greenEye, blueEye));
pixels.show();
}
// ------------------------------------------------------------------------
// flashEyes
// ------------------------------------------------------------------------
void flashEyes(unsigned long flashLength){
unsigned long stopFlash = millis() + flashLength;
while (millis() < stopFlash){
showPixels(255, 0, 0, 100, 50, 0); // bright red eyes, dim yellow mouth
delay(200);
showPixels(90, 0, 0, 100, 50, 0); // dim red eyes, dim yellow mouth
delay(200);
}
}
// ------------------------------------------------------------------------
// speak
// speach from JQ6500 and neopixel output to eyes and mouth
// ------------------------------------------------------------------------
void speak(int speechTrack){
if (speechTrack > 0 & speechTrack < TRACKS){
mp3.playFileByIndexNumber(speechTrack);
unsigned long stopSpeech = millis() + track[speechTrack];
if (anger[speechTrack] == false){
while (millis() < stopSpeech){
showPixels(90, 0, 0, 255, 50, 0); // dim red eyes, bright orange mouth
delay(200);
showPixels(90, 0, 0, 100, 50, 0); // dim red eyes, dim yellow mouth
delay(200);
}
}
if (anger[speechTrack] == true){
while (millis() < stopSpeech){
showPixels(255, 0, 0, 255, 50, 0); // bright red eyes, bright orange mouth
delay(200);
showPixels(90, 0, 0, 100, 50, 0); // dim red eyes, dim yellow mouth
delay(200);
}
}
}
if (speechTrack == 0){
flashEyes(track[0]);
}
}
// -----------------------------------------------------------------
// startWiFi
// -----------------------------------------------------------------
void startWiFi(){
if (DEBUG == true){
Serial.print("\nConnecting to: ");
Serial.println(ssid);
}
while ( WiFi.status() != WL_CONNECTED) {
WiFi.begin(SECRET_SSID, SECRET_PASS);
}
if (DEBUG == true){
Serial.println("\nWiFi connected");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
}
server.begin();
}
// -----------------------------------------------------------------
// doWebPage
// Updates the commandNumber using a simple web server
// -----------------------------------------------------------------
void doWebPage() {
WiFiClient client = server.available();
if (client) {
while (client.connected()) {
if (client.available()) {
char c = client.read();
//read char by char the HTTP request
if (inputString.length() < 100) {
//store characters to string
inputString += c;
}
//if HTTP request has ended
if (c == '\n') {
if (DEBUG == true) {
Serial.print("Inside doWebPage: ");
Serial.println(inputString);
}
// response header
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
client.println("Connection: close");
client.println();
// web page
client.println("<HTML>");
client.println("<HEAD>");
client.println("<TITLE>Robot Santa</TITLE>");
client.println("</HEAD>");
client.println("<BODY>");
client.println("<H1>Robot Santa Entry Commands</H1>");
// FIX THIS! FIX THIS! FIX THIS! FIX THIS!
client.println("<FORM ACTION=\"http://10.0.0.202\" method=get >");
client.println("Enter Command Number (0 to 59): <INPUT TYPE=TEXT NAME=\"COMMAND\" VALUE=\"\" SIZE=\"25\" MAXLENGTH=\"50\">");
client.println("<INPUT TYPE=SUBMIT NAME=\"submit\" VALUE=\"SUBMIT\">");
client.println("</FORM>");
client.println("");
client.println("</BODY>");
client.println("</HTML>");
// find start of the integer command
int i;
for (i = 0; i < inputString.length(); i++) {
if (isdigit(inputString[i]) == true){
break;
}
}
// remove preceeding characters to integer command
inputString = inputString.substring(i);
// convert string to integer
commandNumber = inputString.toInt();
delay(1);
//stop client
client.stop();
//clear string for next read
inputString="";
}
}
}
}
}
In all, there are 60 commands for the robot. The speech unit uses my voice lowered in frequency with Audacity software and stored on the JQ6500. Additional personality is given to the robot by flashing the LEDs in his eyes and inside his mouth. This turned out very good in my opinion - the effect is very much like that in the Futurama series.
Why, Oh Why, Did You Do This:
It needed to be done. Children everywhere are unable to visit Santa Claus (Father Christmas, Kris Kringle, Père Noël, Weihnachtsmann, Babbo Natale, and so on) this season due to pandemic restrictions. With my creation, a mall Santa Claus can set up a one-way camera so that they can see and hear the child while controlling Robot Santa from the comfort of their own home. Thus protecting both Santa's helper and the child.
Before going commercial I tried it out in the lab on my other robots as shown in the short 2-minute video below.
Looks like a bit more adjustment is needed on the anger limiting module before using it with children.
Conclusion:
I am pleased with how it came out.
The design was drawn by eye and if making it again I would make the base wider. The proportions in the cartoon series change depending on the scene and angle and the intent of the artist so proportions are somewhat subjective. Tabs on the ears for alignment to the head would be a good idea. Other than that there is not a whole lot I would change.
For those who prefer more traditional gifts, I created "Flower Pot for a Cat Lover" here.
Thanks for reading. Your thoughts and comments are always welcome.
Related Links
Top Comments