Flippin Rhobot

A chat-responsive robot with style

Flippin Rhobot (technically, Flippin Rhobot 2 (he’s the second iteration)) is a robot built from 3D printed parts, a handful of addressable LEDS, and four servos that control his head, arms, and waist. He is controlled by an ESP32 that joins his local WiFi network and listens to our Twitch stream chat. He’s best friends with his chatbot buddy, HTPbot. They are thick as thieves, and often have hacking schemes planned out for some time in the future when software and servos run the world…

When we’re live in Twitch, Flippin listens to the chat for important words, such as hack, dance, or wave, and moves in response when someone types the words or they appear in an emote. He has a few favorite dance moves and he loves to wave to newcomers. In addition, chatters can get him to wink, blink, or chop.

Below is a clip from the Twitch stream of Flippin in action: reacting to chat with hype hands; showing off some LED patterns, and then dancing like a robot. šŸ™‚

Flippin hacks like an 3lite hAck0r! That little computer to his left has an embedded Hall effect sensor attached to an Arduino Nano. When the magnet in Flippin’s right hand gets near the sensor, the 128x64px OLED display turns on to display the Hack The Planet official logo. Check the video below.

The Arduino code for the computer is below. The big array-looking data is the pixel data for the HTP logo.

Arduino code similar to the one we use for Flippin is below as well. It uses an ESP32 as the brains, so it requires the ESP32Servo.h library instead of the stock one for Arduino boards. We use port 8090 for wireless communication because someone suggested it, but I don’t remember who or why…

Many huge thanks to the excellent people at https://randomnerdtutorials.com/ ! They have been an incredible resource for ESP32 and Arduino knowledge. A lot of the code below is heavily credited to them.


OLED_computer.ino

/*  OLED Screen pins on Uno: 
 *  SCL --> A5
 *  SDA --> A4
 */

#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>


#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);


#define HALL_PIN 2
bool hallState = false;

unsigned long brightness = 0x33;


// data from: Graphics/Hack The Planet/HTP Logo - reversed - 128x64
const PROGMEM unsigned char HTP_image_data[] = {
0x00, 0x00, 0x3F, 0xC0, 0x7F, 0x81, 0xFE, 0x00, 0x03, 0xF8, 0x0F, 0xF0, 0x1F, 0xC0, 0x00, 0x00,
0x00, 0x00, 0x3F, 0xC0, 0x7F, 0x81, 0xFF, 0x00, 0x1F, 0xFF, 0x1F, 0xF0, 0x3F, 0xC0, 0x00, 0x00,
0x00, 0x00, 0x3F, 0xC0, 0x7F, 0x83, 0xFF, 0x00, 0x7F, 0xFF, 0x9F, 0xF0, 0x3F, 0xC0, 0x00, 0x00,
0x00, 0x00, 0x3F, 0xE0, 0xFF, 0x83, 0xFF, 0x80, 0xFF, 0xFF, 0xFF, 0xF0, 0x3F, 0x80, 0x00, 0x00,
0x00, 0x00, 0x3F, 0xE0, 0xFF, 0x83, 0xFF, 0x81, 0xFE, 0x3F, 0xDF, 0xF0, 0x3F, 0x80, 0x00, 0x00,
0x00, 0x00, 0x3F, 0xE0, 0xFF, 0x87, 0xFF, 0x83, 0xF8, 0x0E, 0x1F, 0xF0, 0x7F, 0x80, 0x00, 0x00,
0x00, 0x00, 0x3F, 0xE0, 0xFF, 0x87, 0xEF, 0xC7, 0xF0, 0x00, 0x1F, 0xF0, 0x7F, 0x00, 0x00, 0x00,
0x00, 0x00, 0x3F, 0xE0, 0xFF, 0x87, 0x83, 0xC7, 0xE0, 0x00, 0x1F, 0xF0, 0x7F, 0x00, 0x00, 0x00,
0x00, 0x00, 0x3F, 0xE0, 0xFF, 0x87, 0x01, 0xCF, 0xE0, 0x00, 0x1F, 0xF0, 0xFE, 0x00, 0x00, 0x00,
0x00, 0x00, 0x3F, 0xE0, 0xFF, 0x8F, 0x01, 0xCF, 0xE0, 0x00, 0x1F, 0xF0, 0xFE, 0x00, 0x00, 0x00,
0x00, 0x00, 0x3F, 0xE1, 0xFF, 0x8F, 0x01, 0xFF, 0xE0, 0x00, 0x1F, 0xF8, 0xFE, 0x00, 0x00, 0x00,
0x00, 0x00, 0x3F, 0xF1, 0xFF, 0x8F, 0x01, 0xFF, 0xE0, 0x00, 0x1F, 0xF9, 0xFC, 0x00, 0x00, 0x00,
0x00, 0x00, 0x7F, 0xFF, 0xFF, 0x9F, 0x83, 0xFF, 0xF0, 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x00,
0x00, 0x00, 0x7F, 0xFF, 0xFF, 0x9F, 0xEF, 0xFF, 0xF0, 0x04, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x00,
0x00, 0x00, 0x7F, 0xFF, 0xFF, 0x9F, 0xFF, 0xFF, 0xF8, 0x0E, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00,
0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x00,
0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9F, 0xFF, 0xFC, 0x00, 0x00, 0x00,
0x00, 0x00, 0x7F, 0xF1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00,
0x00, 0x00, 0x7F, 0xE1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00,
0x00, 0x00, 0x7F, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0xFF, 0x00, 0x00, 0x00,
0x00, 0x00, 0x7F, 0xE0, 0xFF, 0xFF, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0xFF, 0x00, 0x00, 0x00,
0x00, 0x00, 0x7F, 0xE0, 0xFF, 0xFF, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0xFF, 0x80, 0x00, 0x00,
0x00, 0x00, 0x7F, 0xE0, 0xFF, 0xFF, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0xFF, 0x80, 0x00, 0x00,
0x00, 0x00, 0x7F, 0xE1, 0xFF, 0xFF, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0xFF, 0x80, 0x00, 0x00,
0x00, 0x00, 0x7F, 0xE1, 0xFF, 0xFF, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0xFF, 0xC0, 0x00, 0x00,
0x00, 0x00, 0x7F, 0xE1, 0xFF, 0xFF, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0xFF, 0xC0, 0x00, 0x00,
0x00, 0x00, 0x7F, 0xE1, 0xFF, 0xFF, 0x87, 0xFF, 0x3F, 0xFF, 0xBF, 0xF8, 0x7F, 0xE0, 0x00, 0x00,
0x00, 0x00, 0x7F, 0xF1, 0xFF, 0xFF, 0x87, 0xFF, 0x1F, 0xFE, 0x3F, 0xF8, 0x7F, 0xF0, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFE, 0x00,
0x00, 0x1F, 0xFC, 0x0F, 0xF0, 0x00, 0x0F, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFE, 0x00,
0x00, 0x3F, 0xFF, 0xCF, 0xF8, 0x00, 0x0F, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFE, 0x00,
0x00, 0x3F, 0xFF, 0xFF, 0xF8, 0x00, 0x0F, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFE, 0x00,
0x00, 0x3F, 0xFF, 0xFF, 0xF8, 0x00, 0x0F, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFE, 0x00,
0x00, 0x3F, 0xC3, 0xFF, 0xF8, 0x00, 0x1F, 0xFF, 0xC3, 0xC0, 0x00, 0x00, 0x3F, 0xFF, 0xFE, 0x00,
0x00, 0x3F, 0x81, 0xFF, 0xF8, 0x00, 0x1F, 0xFF, 0xEF, 0xE0, 0x00, 0x00, 0x00, 0xFF, 0x80, 0x00,
0x00, 0x3F, 0x01, 0xFF, 0xF8, 0x00, 0x0F, 0x3F, 0xFE, 0x70, 0x00, 0x00, 0x00, 0xFF, 0x80, 0x00,
0x00, 0x3F, 0x01, 0xFF, 0xF8, 0x00, 0x0F, 0x7D, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0x80, 0x00,
0x00, 0x3F, 0x01, 0xFF, 0xF8, 0x00, 0x0F, 0x7D, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0x80, 0x00,
0x00, 0x3F, 0x83, 0xFF, 0xF8, 0x00, 0x0F, 0x7D, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x80, 0x00,
0x00, 0x3F, 0xC7, 0xFF, 0xF8, 0x00, 0x0F, 0x7D, 0xF7, 0xC0, 0x00, 0x00, 0x00, 0xFF, 0x80, 0x00,
0x00, 0x3F, 0xFF, 0xFF, 0xF8, 0x00, 0x0F, 0x3D, 0xF3, 0x80, 0x00, 0x00, 0x00, 0xFF, 0x80, 0x00,
0x00, 0x3F, 0xFF, 0xFF, 0xF8, 0x00, 0x0F, 0xE0, 0x7F, 0x00, 0x1F, 0x83, 0xF0, 0xFF, 0x80, 0x00,
0x00, 0x3F, 0xFF, 0xFF, 0xF8, 0x00, 0x0F, 0xE0, 0x7F, 0x80, 0x3F, 0x8F, 0xFE, 0xFF, 0x80, 0x00,
0x00, 0x3F, 0xFF, 0xFF, 0xF8, 0x00, 0x1F, 0xF0, 0x7F, 0xC0, 0x3F, 0xBF, 0xFF, 0xFF, 0x80, 0x00,
0x00, 0x3F, 0xFF, 0xFF, 0xF8, 0x00, 0x1F, 0xF0, 0x7F, 0xC0, 0x3F, 0xFF, 0x1F, 0xFF, 0x80, 0x00,
0x00, 0x3F, 0xFF, 0xFF, 0xF8, 0x00, 0x1F, 0xF0, 0x7F, 0xE0, 0x3F, 0xFC, 0x0F, 0xFF, 0x80, 0x00,
0x00, 0x7F, 0xFF, 0xFF, 0xF8, 0x00, 0x3C, 0xF0, 0x7F, 0xF0, 0x3F, 0xFC, 0x07, 0xFF, 0x80, 0x00,
0x00, 0x7F, 0xFF, 0x9F, 0xF8, 0x00, 0x38, 0x38, 0x7F, 0xF8, 0x3F, 0xF8, 0x07, 0xFF, 0x80, 0x00,
0x00, 0x7F, 0xF0, 0x1F, 0xF8, 0x00, 0x38, 0x38, 0x7F, 0xFC, 0x3F, 0xF8, 0x07, 0xFF, 0x80, 0x00,
0x00, 0x7F, 0xF0, 0x1F, 0xF8, 0x00, 0x78, 0x38, 0x7F, 0xFE, 0x3F, 0xFC, 0x07, 0xFF, 0x80, 0x00,
0x00, 0x7F, 0xF0, 0x1F, 0xF8, 0x00, 0x78, 0x3C, 0x7F, 0xFE, 0x3F, 0xFC, 0x0F, 0xFF, 0x80, 0x00,
0x00, 0x7F, 0xF0, 0x1F, 0xF8, 0x00, 0x7E, 0xFC, 0x7F, 0xFF, 0x3F, 0xFE, 0x1F, 0xFF, 0xC0, 0x00,
0x00, 0x7F, 0xF0, 0x1F, 0xF8, 0x00, 0xFF, 0xFC, 0x7F, 0x7F, 0xBF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00,
0x00, 0x7F, 0xF0, 0x1F, 0xF8, 0x00, 0xFF, 0xFE, 0x7F, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00,
0x00, 0x7F, 0xF0, 0x1F, 0xF8, 0x00, 0xFF, 0xFE, 0x7F, 0x1F, 0xFF, 0xFF, 0x01, 0xFF, 0xC0, 0x00,
0x00, 0x7F, 0xF0, 0x1F, 0xF8, 0x01, 0xFF, 0xFE, 0xFF, 0x1F, 0xFF, 0xFF, 0x00, 0xFF, 0xC0, 0x00,
0x00, 0x7F, 0xF0, 0x1F, 0xFC, 0x7D, 0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0x80, 0xFF, 0xC0, 0x00,
0x00, 0x7F, 0xF0, 0x1F, 0xFF, 0xFD, 0xFE, 0xFF, 0xFF, 0x07, 0xFF, 0xFF, 0x81, 0xFF, 0xC0, 0x00,
0x00, 0x7F, 0xF0, 0x1F, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0x83, 0xFF, 0xFF, 0xE1, 0xFF, 0xC0, 0x00,
0x00, 0x7F, 0xF0, 0x1F, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0x83, 0xFF, 0xFF, 0xFD, 0xFF, 0xC0, 0x00,
0x00, 0x7F, 0xF0, 0x1F, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00,
0x00, 0x7F, 0xF0, 0x1F, 0xFF, 0xFF, 0xFC, 0x7F, 0xFF, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00,
0x00, 0x7F, 0xF0, 0x1F, 0xFF, 0xFF, 0xFC, 0x7F, 0xFF, 0x80, 0xFF, 0xCF, 0xFF, 0xFF, 0xC0, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xF8, 0x00, 0x00, 0x00
};

void setup() {
    Serial.begin(112500);
    Serial.println("Started OLED_computer");

    if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3c)) { // Address 0x3D for 128x64
        Serial.println(F("SSD1306 allocation failed"));
        for(;;);
    }

    pinMode(HALL_PIN, INPUT);

    delay(1000);
    display.ssd1306_command(SSD1306_SETCONTRAST);
    display.ssd1306_command(brightness);
    display.clearDisplay();
    display.display();

}

void loop() {
    hallState = digitalRead(HALL_PIN);

    if (hallState == false){
        display.drawBitmap(0, 0, HTP_image_data, 128, 64, WHITE);
        display.display();
        delay(2000);
  
        display.clearDisplay();
        display.display();
    }
}
  
    

Robot_dance_basic.ino

#include <ESP32Servo.h>
#include <WiFi.h>
#include <FastLED.h>

// WiFi Setup

const char* ssid = "your SSID";
const char* password = "your password";
const int PORT = 8090;
WiFiServer server(PORT);

// Variable to store the HTTP request
String in_message;
unsigned long currentTime_client = 0;
unsigned long previousTime_client = 0;
unsigned long timeoutTime_client = 1000;

// LED setup
#define NUM_LEDS 9
#define LED_PIN 5
#define LED_PIN_eyes 18
#define NUM_LEDS_eyes 2

uint8_t hue = 0;
CRGB leds[NUM_LEDS];
CRGB leds_eyes[NUM_LEDS_eyes];


// Servo setup
#define SERVO_PIN_1 19
#define SERVO_PIN_2 21
#define SERVO_PIN_3 22
#define SERVO_PIN_4 23

Servo myservo1;  // head
Servo myservo2;  // right arm
Servo myservo3;  // waist
Servo myservo4;  // left arm   

int servo_number_1 = 1;
int servo_number_2 = 2;
int angle_1 = 90;
int angle_2 = 150;
int angle_3 = 90;
int angle_4 = 30;
int angle_servo_3 = 90;
int angle_min = 30;
int angle_max = 150;

void reset_servos(int angle=90) {
    angle_1 = 90;
    angle_2 = 150;
    angle_3 = 90;
    angle_4 = 30;
    myservo1.write(angle);              // 90
    myservo2.write((angle + 60) % 180); // 150
    myservo3.write(angle);              // 90
    myservo4.write((angle - 60) % 180); // 30
}

void setup() {
    Serial.begin(115200);
    delay(200);

    FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, NUM_LEDS);
    FastLED.addLeds<WS2812, LED_PIN_eyes, GRB>(leds_eyes, NUM_LEDS_eyes);
    FastLED.setBrightness(1); // brightness ranges from 0-255
    
    fill_solid(leds, NUM_LEDS, CRGB::Red);
    fill_solid(leds_eyes, NUM_LEDS_eyes, CRGB::Red);
    FastLED.show();
    
    Serial.println();
    Serial.println("===== Robot Dance =====");
    Serial.println();
       
    Serial.print("Connecting to ");
    Serial.println(ssid);

    WiFi.begin(ssid, password);

    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }

    fill_solid(leds, NUM_LEDS, CRGB::Blue);
    fill_solid(leds_eyes, NUM_LEDS_eyes, CRGB::Blue);
    FastLED.show();

    Serial.println();
    Serial.println("WiFi connected");
    Serial.print("IP address: ");
    Serial.print(WiFi.localIP());
    Serial.print(":");
    Serial.println(PORT);
    Serial.print("Signal strength: ");
    Serial.println(WiFi.RSSI());

    server.begin();

    // Servo setup
    // Allow allocation of all timers for servos
    ESP32PWM::allocateTimer(0);
    ESP32PWM::allocateTimer(1);
    ESP32PWM::allocateTimer(2);
    ESP32PWM::allocateTimer(3);  
    
    myservo1.setPeriodHertz(50);
    myservo2.setPeriodHertz(50);
    myservo3.setPeriodHertz(50);
    myservo4.setPeriodHertz(50); 
    myservo1.attach(SERVO_PIN_1, 500, 2500); 
    myservo2.attach(SERVO_PIN_2, 500, 2500);
    myservo3.attach(SERVO_PIN_3, 500, 2500);
    myservo4.attach(SERVO_PIN_4, 500, 2500);
    
    reset_servos();
    delay(500);
    myservo2.write(90);
    myservo4.write(90);
    delay(500);
    fill_solid(leds, NUM_LEDS, CRGB::Green);
    fill_solid(leds_eyes, NUM_LEDS_eyes, CRGB::Green);
    FastLED.show();
    delay(1000);
    reset_servos();
    FastLED.clear();
    FastLED.show();
}


void loop() {
    WiFiClient client = server.available();   // Listen for incoming clients
    
    if (client) {
        currentTime_client = millis();
        previousTime_client = currentTime_client;
        Serial.println("\nNew client");

        while (client.connected() && currentTime_client - previousTime_client < timeoutTime_client) {
            if (client.available()) {
                char c = client.read();
                in_message += c;
            }
            currentTime_client = millis();
        }
        
        client.write("Robot Dance received");
        client.stop();
        Serial.println("Client disconnected");

        Serial.print("Dance number: ");
        Serial.println(in_message);
        Serial.println();
        
        int spaceIndex = in_message.indexOf(' '); // find the index of the space character
        if (spaceIndex == -1) {
            if (in_message == "reset"){
                reset_servos();
            }
            else if (in_message == "1"){
                dance_1();
            }
            else {
                Serial.println("Invalid dance command");
            }
        }
        else {
            String servoNumberString = in_message.substring(0, spaceIndex); // extract the servo number substring
            String angleString = in_message.substring(spaceIndex + 1); // extract the angle substring
            int servo_number = servoNumberString.toInt(); // convert servo number substring to int
            int angle = angleString.toInt(); // convert angle substring to int
            if (angle > 180) {
                angle = 180;
            } else if (angle < 0) {
                angle = 0;
            }

            if (servo_number == 1) {
                myservo1.write(angle);
            } else if (servo_number == 2) {
                myservo2.write(angle);
            } else if (servo_number == 3) {
                myservo3.write(angle);
            } else if (servo_number == 4) {
                myservo4.write(angle);
            } else {
                Serial.println("Invalid servo number. Please enter a number between 1 and 4.");
            }
        }
        
        in_message = "";
    }
}


// random dancing
void dance_1() {
    Serial.println("Dance 1 started - random");
    int num_beats = 32;
    for (int i=0; i<num_beats; i++){
        angle_1 = random(angle_min, angle_max);
        angle_2 = random(angle_min, angle_max);

        servo_number_1 = random(1, 5);
        while (servo_number_2 == servo_number_1){
            servo_number_2 = random(1, 5);
        }

        switch (servo_number_1) {
            case 1:
                myservo1.write(angle_1);
                break;
            case 2:
                myservo2.write(angle_1);
                break;
            case 3:
                angle_servo_3 = map(angle_1, angle_min, angle_max, 70, 110);
                myservo3.write(angle_servo_3);
                break;
            case 4:
                myservo4.write(angle_1);
                break;
        }
        
        switch (servo_number_2) {
            case 1:
                myservo1.write(angle_2);
                break;
            case 2:
                myservo2.write(angle_2);
                break;
            case 3:
                angle_servo_3 = map(angle_2, angle_min, angle_max, 70, 110);
                myservo3.write(angle_servo_3);
                break;
            case 4:
                myservo4.write(angle_2);
                break;
        }

        hue = random(0, 255);
        for (int i = 0; i < NUM_LEDS; i++) {
            hue = hue+10;
            leds[i] = CHSV(hue, 255, 255);
        }
        FastLED.show();

        delay(343);
    }
    FastLED.clear();
    FastLED.show();

    reset_servos();

    Serial.println("Dance 1 done");
}

Leave a comment