X2 humanoid robot

[Update: Chapter PROGRAMMING - Chat bot mode]

Introduction

x2 is a small humanoid robot platform for my current research. The specifications are as following:

  • Arduino Mega micro controller, maybe upgrade to Arduino Due
  • Adafruit 16-Channel 12-bit PWM/Servo Driver - I2C interface
  • HC-06 HC-05 Bluetooth module to connect with other devices
  • Differential wheeled
  • 2 DOF head
  • Optional two 3 DOF arms
  • Nokia 5110 LCD to display GUI/robot OS/emotional states of the robot
  • TTS module EMIC-2
  • 3 HC-SR04 distance sensors and a 3-axis accelerometer (ADXL335)
  • On-board mini keyboard to communicate with the robot
  • TTL camera for snapshots, motion detection
  • Micro SD card
  • DS1307 RTC and DS18B20 temperature sensor
  • Supervised machine learning algorithm via Stochastic Learning Automaton
  • String To Math Parser

Mechanics

The main frame of the robot is made from MicroRax beams. The drive wheels and the free turning wheel are from Makeblock. The motors of the differential wheeled robot are two for continuous rotation modified Hitec HS-645 MG servos, which providing more than enough torque. The wheels are attached to the servos by aluminium servo horns:

Front side of the robot:

Backside of the robot:

Several objects as seen below are 3-D printed. I draw the parts with Sketchup Make 2014, exported the files to .STL format via STL-plugin and sent them off to a 3-D printing factory in Canton.From left to right: Camera holder, sound box for loudspeaker, assembling ring for loudspeaker, cover for sound box, HC-SR04 distance sensor holder, Nokia 5110 LCD holder.

Received 3-D printed parts, still not cleaned up:

The complete 3-D drawing can be found in the attachment in case you want to explore the construction of the robot further.

MicroRax frame assembled:

Further assembly:

Electronics

The system design looks as following:

I designed two PCB's for the robot. One is a shield for the Arduino Mega, contains micro SD card, RTC, LCD level shifter/backlight PWM, temperature sensor and power management for the servos, 5V and 3.3V devices, the other is an onboard keyboard based on the CD4021B 8-Stage Static Shift Register, similar to the ALPHAZED keyboard. I designed both PCB's with Fritzing, the extended Gerber files were then sent off to a Chinese PCB manufacturer.

Routed PCB's before copper fill and manufactured boards...

PCB's populated and tested:

After I got two faulty HC-05 carrier boards for the HC-05 Bluetooth module I decided to design my own. Routed PCB's before copper fill and manufactured board, featuring KEY (configure HC-05 module) and STATE (pairing is done or not) breakout, STATUS LED (indicator of work mode), voltage divider for the HC-05 RX and CE (chip enable by a PNP transistor to supply power) to switch the HC-05 off whenever I want to save power or upload code by serial (does not work if HC-05 is connected to hardware serial and running same time). And it has mounting holes. A good PCB  design starts with mounting holes.


Populated PCB ready for testing...


Programming

Stochastic Learning Automaton:

A stochastic learning automaton is used to obtain supervised machine learning. The robot has a given set of possible actions, and every of these actions is tagged with the same probability at start-up. An action is then randomly selected when an according event occurs and the the robot waits for input from the user or evaluates by itself by given targets as to whether it was a good action or not. If it was good, this action will be tagged with a higher probability while the other actions will be tagged with a lower probability to be chosen if this even occurs again and vice versa. 

Beside learning to avoid obstacles the algorithm will be used in the chat mode. Instead of actions the robot chooses randomly topics. According to your response the robot learns after a while, about which topics you want to talk and about which topics not so much.

String To Math Parser:

One of x2's modes is a calculator, a simple equation solver which supports only integers, the operators '+', '-', '*' and '/', one prefix ('+' or '-') per integer and no white space, for example -3*5/2-3-1/-6, with the implemented rule: multiplication and division first, then addition and subtraction. The algorithm works as following:

  • Isolate all integers from left to right from the String, convert them to positive or negative floats, store them and their end position number in according arrays
  • Reciprocal all floats which have the pre operator '/'. Now only multiplication and addition is left
  • Compute the multiplication bulks in the equation, e.g. -3*5*0.5 = -7.5, store them in according addition array
  • Add the integers which belong not to multiplication bulks also to this addition array
  • Sum this addition array up
  • Check for errors, e.g. division by 0. If no error, result is sum of addition array, else display error

I have attached a first draft of the Arduino source code and added it below. You can test it by just using the serial monitor. I am sure the code can still be simplified and cleaned up. For bug reports and suggestions, feel free to post a comment.

  1 // Supports only integers, the operators '+', '-', '*' and '/' and one prefix ('+' or '-') per integer; no white space.
  2 // Open Serial monitor and type in equation like  -3*5/2-3-1/-6 without '=', then and press enter.
  3 // All valid operators will be deleted automatically after last integer in the equation, e.g. -3*5/2-3-1/-6-- will be
  4 // interpreted as -3*5/2-3-1/-6
  5 
  6 String EquationStringBuffer = ""; // stores the user input
  7 String EquationString = ""; // stores the equation
  8 String Number = ""; // stores an integer in the equation
  9 float operandsArray [21]; // stores the operands of the equation
 10 int numberPositionEnd [21]; // stores the end position of numbers in the String
 11 float MultiplicationArray [21]; // stores numbers which need to be multiplicated
 12 float AdditionArray [21]; // stores numbers which need to be added
 13 char* ErrorMessage[] = {"Error. Division by 0", "Error. Too many Integers. Maximum 20", "Error. Integer too long"}; 
 14 
 15 void setup() {
 16   Serial.begin (9600);
 17 }
 18 
 19 void loop() {
 20   while (1) {
 21     while (Serial.available()) {
 22     delay(3);  
 23     if (Serial.available() > 0) {
 24       char c = Serial.read();  
 25       EquationStringBuffer += c; 
 26     } 
 27   }
 28   if (EquationStringBuffer.length() > 0) {
 29     EquationString = EquationStringBuffer;
 30     Serial.print(EquationString);
 31     Serial.print(" = ");
 32     EquationStringBuffer = "";
 33     break;
 34   }
 35  }
 36   int ErrorCode = 0;
 37   int numberCounter = 0;
 38   boolean digitCounter = false;
 39   for (int i = (EquationString.length()-1); i > -1; i --) { // delete any operators after last number in EquationString
 40     if (EquationString.charAt(i) == '-' || EquationString.charAt(i) == '+' || EquationString.charAt(i) == '*' || EquationString.charAt(i) == '/') {
 41       EquationString = EquationString.substring(0, i);
 42     } else break;
 43   }
 44   EquationString += '-'; // add this character on the end of the string to recognize last number of equation
 45   for (int i = 0; i < EquationString.length(); i ++) { // there must start numbers
 46     if (EquationString.charAt(i) == '0' || EquationString.charAt(i) == '1' || EquationString.charAt(i) == '2' || EquationString.charAt(i) == '3' || EquationString.charAt(i) == '4'
 47     || EquationString.charAt(i) == '5' || EquationString.charAt(i) == '6' || EquationString.charAt(i) == '7'|| EquationString.charAt(i) == '8' || EquationString.charAt(i) == '9') {
 48       if (EquationString.charAt(i - 1) == '-') {
 49         Number += '-';
 50         Number += EquationString.charAt(i);
 51         digitCounter = true;
 52       } else {
 53         Number += EquationString.charAt(i);
 54         digitCounter = true;
 55       }  
 56     } else if (EquationString.charAt(i) != '0' && EquationString.charAt(i) != '1' && EquationString.charAt(i) != '2' && EquationString.charAt(i) != '3' && EquationString.charAt(i) != '4'
 57     && EquationString.charAt(i) != '5' && EquationString.charAt(i) != '6' && EquationString.charAt(i) != '7'&& EquationString.charAt(i) != '8' && EquationString.charAt(i) != '9' && 
 58     digitCounter == true) {
 59       char Number_buffer[Number.length() + 1];
 60       Number.toCharArray(Number_buffer, Number.length() + 1); // copies the string's characters to Number_buffer char array
 61       float NumberToArray = atof(Number_buffer); // convert  to float
 62       if (NumberToArray > 999999.0) ErrorCode = 3;
 63       operandsArray [numberCounter] = NumberToArray; // store in operandsArray 
 64       numberPositionEnd [numberCounter] = i;
 65       numberCounter ++;
 66       digitCounter = false;
 67       Number = "";
 68     }
 69   }
 70   if (numberCounter > 20) ErrorCode = 2;
 71   int MultiplicationCounter = 0;
 72   int AdditionCounter = 0;
 73   for (int i = 0; i < numberCounter; i ++) { 
 74     if (EquationString.charAt(numberPositionEnd [i]) == '*' || EquationString.charAt(numberPositionEnd [i]) == '/') {
 75       if (EquationString.charAt(numberPositionEnd [i]) == '*') {
 76         MultiplicationArray [MultiplicationCounter] = operandsArray [i];
 77         MultiplicationCounter ++;
 78       }
 79       if (EquationString.charAt(numberPositionEnd [i]) == '/') {
 80         if (operandsArray [i + 1] == 0.0) ErrorCode = 1;
 81         operandsArray [i + 1] = 1/operandsArray [i + 1]; // reciprocal next number 
 82         MultiplicationArray [MultiplicationCounter] = operandsArray [i];
 83         MultiplicationCounter ++;
 84       }
 85     }
 86     float MultificationsBuffer = 1.0;
 87     if (EquationString.charAt(numberPositionEnd [i]) != '*' && EquationString.charAt(numberPositionEnd [i]) != '/' && MultiplicationCounter > 0) {
 88        MultiplicationArray [MultiplicationCounter] = operandsArray [i];
 89        for (int k = 0; k < MultiplicationCounter + 1; k ++) {
 90          MultificationsBuffer = MultificationsBuffer * MultiplicationArray [k];
 91       }
 92       MultiplicationCounter = 0;
 93       AdditionArray [AdditionCounter] = (MultificationsBuffer);
 94       AdditionCounter ++;
 95     } 
 96     if (i == 0 && EquationString.charAt(numberPositionEnd [0]) != '*' && EquationString.charAt(numberPositionEnd [0]) != '/') {
 97       AdditionArray [AdditionCounter] = operandsArray [0];
 98       AdditionCounter ++;
 99     }
100     if (i == (numberCounter - 1) && numberCounter > 1 && EquationString.charAt(numberPositionEnd [numberCounter - 2]) != '*' 
101     && EquationString.charAt(numberPositionEnd [numberCounter - 2]) != '/') {
102       AdditionArray [AdditionCounter] = operandsArray [i];
103       AdditionCounter ++;
104     }
105     if (i > 0 && i != numberCounter - 1 && EquationString.charAt(numberPositionEnd [i]) != '*' && EquationString.charAt(numberPositionEnd [i]) != '/' 
106     && EquationString.charAt(numberPositionEnd [i - 1]) != '*' && EquationString.charAt(numberPositionEnd [i - 1]) != '/' ) { 
107       AdditionArray [AdditionCounter] = operandsArray [i]; 
108       AdditionCounter ++;
109     }
110   }
111   float AdditionsBuffer = 0;
112   for (int n = 0; n < AdditionCounter; n ++) {
113     AdditionsBuffer = AdditionsBuffer + AdditionArray [n];
114   }
115   switch (ErrorCode) {
116     case 0:
117     Serial.println(AdditionsBuffer); // print result
118     break;
119     case 1:
120     Serial.println(ErrorMessage[0]);
121     break;
122     case 2:
123     Serial.println(ErrorMessage[1]);
124     break;
125     case 3:
126     Serial.println(ErrorMessage[2]);
127     break;
128   }
129 }

Date & Time:

x2 is now able to compute the current date, day of the week of any date (day of the week of current date is supported by the Adafruit RTClib), leap year, lunar phase at present, time in 24 h format and time in 12 h format, which features an analog watch. Again, only Graphics Primitives are used for the lunar phases and analog watch as for x2's emotional expressions. I compute the coordinates of the second/minute/hour hand of the analog watch via polar coordinates of a circle, which seems to be the easiest approach:

Demo video below.

Markov chain text generator:

I am experimenting with a Markov chain text generator for the chat mode of x2. Following the explanations here, I wrote a first (and dirty) draft sketch of a simplified Markov chain text generator:

 1 // https://www.cs.umd.edu/class/winter2012/cmsc389C/Projects/P1/P1.html
 2 
 3 #include <Entropy.h>
 4 String User = "THE DOG IS YELLOW THE CAT IS YELLOW SO IS THE DOG THE CAT"; // input from user or text file
 5 String Ending = "THE_END"; // add  terminal word, can be changed from a randomly chosen String
 6 char *words[500]; // storage buffer for the words in String/array
 7 char *leading_words[500]; // store leading words
 8 char *chains[500]; // store chains
 9 int chainNumbers[500];
10 
11 void setup() {
12   Serial.begin (9600);
13   Entropy.Initialize();
14   User += ' ';
15   User += Ending; // add terminal word to input String
16 }
17 
18 void loop() {
19   int chain_end = 0;
20   int output_word_counter = 0;
21   int leading_word_number = 0;
22   int random_word;
23   int word_counter = 0; // count words in String/array
24   int newword = 0;
25   int strcmp_counter = 0;
26   int leading_word_counter = 0;
27   char inputchar[User.length() + 1]; // define array to convert String to it
28   char *pch;
29   User.toCharArray(inputchar, User.length() + 1); // convert String to array
30   pch = strtok (inputchar, " ");
31   while (pch != NULL) { // save all words in the array 'words'
32     words[word_counter] = pch;
33     pch = strtok (NULL, " ");
34     word_counter ++;
35   }
36   for (int i = 0; i < word_counter; i ++) {
37     for (int j = 0; j < i; j ++) {
38       if (i != 0 && !strcmp(words[j],words[i])) strcmp_counter ++;
39     }
40     if (strcmp_counter == 0 && i != word_counter - 1) { // ignore last word if it is unique
41       leading_words[leading_word_counter] = words[i];
42       leading_word_counter ++;
43     }
44     strcmp_counter = 0; // reset counter
45     }
46     for (int m = 0; m < leading_word_counter; m ++) {
47       for (int n = 0; n < word_counter; n ++) {
48         if (!strcmp(words[n],leading_words[m])) {
49           chains[chain_end] = words[n + 1];
50           chain_end ++;
51         }
52       }
53       chainNumbers[m] = chain_end;
54     }
55     String Robot_Output = leading_words[leading_word_number];
56     Robot_Output += ' ';
57     while (1) {
58       if (leading_word_number == 0) {
59         if (chainNumbers[leading_word_number] < 2) {
60           Robot_Output += chains[0];
61         } else {
62           random_word = Entropy.random(0,chainNumbers[leading_word_number]);
63           Robot_Output += chains[random_word];
64         }
65       } else {
66         if (chainNumbers[leading_word_number] - chainNumbers[leading_word_number - 1] < 2) {
67           Robot_Output += chains[chainNumbers[leading_word_number - 1]];
68         } else {
69           random_word = Entropy.random(chainNumbers[leading_word_number - 1],chainNumbers[leading_word_number]);
70           Robot_Output += chains[random_word];
71         }
72       }
73       Robot_Output += ' ';
74       for (int i = 0; i < leading_word_counter; i ++) {
75         if (!strcmp(leading_words[i],chains[random_word])) {
76           leading_word_number =  i;
77           break;
78         } 
79       }
80       String EndingBuffer = chains[random_word];
81       if ((leading_word_number == 0 && chainNumbers[leading_word_number] < 2) || 
82       (chainNumbers[leading_word_number] - chainNumbers[leading_word_number - 1]) < 2 ||
83       !EndingBuffer.compareTo(Ending) || Robot_Output.length() >= User.length()) break;
84       output_word_counter ++;
85     } 
86     Serial.print(Robot_Output);
87     Serial.println(" ");
88 }

The generator makes no big sense if the input text is short and lacks therefore of repeating words. A first idea is to add the most common 100 words in English to the user input String or multiply it by an order depending on its length.

Bluetooth:

The Arduino serial monitor does not work reliable as a serial terminal when using Bluetooth. Either the serial port is already in use or the Arduino serial monitor freezes during pairing. Tera Term works fine but you can't submit a string, every single character you press on your keyboard is transmitted immediately. CoolTerm has a line mode where you can type in your string and press then ENTER to transmit it. Perfect for my needs. Below test set-up of my HC-05 carrier PCB and test source code.


1 int ledpin = 13; 2 int enableBTpin = 3; 3 int stateBTpin = 2; 4 char BluetoothData; 5 boolean PairingMode = false; 6 7 void setup() { 8 Serial2.begin(9600); 9 // Serial2.println("Send 1 to switch LED on and 0 to switch LED off"); 10 pinMode(ledpin, OUTPUT); 11 pinMode(enableBTpin, OUTPUT); 12 pinMode(stateBTpin, INPUT); 13 digitalWrite(enableBTpin, 0); // switch BT module on 14 } 15 16 void loop() { 17 if (digitalRead(stateBTpin) == HIGH && PairingMode == false) { 18 Serial2.println("PAIRING DONE!"); 19 PairingMode = true; 20 } 21 if (Serial2.available()){ 22 BluetoothData = Serial2.read(); 23 if(BluetoothData == '1') { 24 digitalWrite(ledpin,1); 25 Serial2.println("LED ON"); 26 } 27 if (BluetoothData == '0') { 28 digitalWrite(ledpin, 0); 29 Serial2.println("LED OFF"); 30 } 31 } 32 delay(100); 33 }

Chat bot mode :

I finally finished a draft version of x2's chat bot mode which uses the Arduino serial monitor. I was inspired by the natural language processing bot ELIZA written by Joseph Weizenbaum between 1964 and 1966. You can find the original code in BASIC attached as a .txt file. After debugging the code every time times when I got unexpected results, I had the idea to manipulate the for statements by purpose to get really unpredictable responses. I will experiment further.

 1 //********************************** LIBRARIES **********************************
  2
  3 // ----------------------------- Random ----------------------------
  4 #include <Entropy.h> // true random lib, see
  5 // http://forum.arduino.cc/index.php/topic,108380.0.html
  6
  7 // ------------------------------ Time -----------------------------
  8 #include <Wire.h>
  9 #include "RTClib.h"
 10 RTC_Millis RTC;
 11
 12 // ------------------------------ Math -----------------------------
 13
 14 #include <Average.h> // performing mathematical analysis of arrays of numbers
 15 // http://playground.arduino.cc/main/average
 16
 17 //*************************** GLOBAL VARIABLES/CONSTANTS *************************
 18
 19 String User; // input from user
 20 String LastInput; // buffers user input
 21 String LastOutput; // buffers robot output
 22
 23 char *conjugations[] = {
 24   "MYSELF", "YOURSELF",
 25   "YOURSELF", "MYSELF",
 26   "MY", "YOUR",   
 27   "YOUR'S", "MINE", 
 28   "YOUR", "MY",
 29   "MINE", "YOUR'S", 
 30   "I MUST", "YOU MUST",
 31   "YOU MUST", "I MUST",
 32   "I'M", "YOU ARE",
 33   "I AM", "YOU ARE",
 34   "YOU'RE", "I AM", 
 35   "YOU ARE", "I AM", 
 36   "ARE YOU", "I AM", 
 37   "I'VE", "YOU HAVE",
 38   "I HAVE", "YOU HAVE",
 39   "YOU'VE", "I HAVE",
 40   "YOU HAVE", "I HAVE",
 41   "I'LL", "YOU WILL",
 42   "I WILL", "YOU WILL",
 43   "YOU'LL", "I WILL",
 44   "YOU WILL", "I WILL",
 45   "YOU WON'T", "I WON'T",
 46   "I WON'T", "YOU WON'T",
 47   "I CAN'T", "YOU CAN'T",
 48   "YOU CAN'T", "I CAN'T",
 49   "I CAN", "YOU CAN",
 50   "YOU CAN", "I CAN",
 51   "I DON'T", "YOU DON'T",
 52   "YOU DON'T", "I DON'T",
 53   "I DO", "YOU DO",
 54   "YOU DO", "I DO",
 55   "DO I", "DO YOU",
 56   "DO YOU", "DO I",
 57   "DID I", "DID YOU",
 58   "DID YOU", "DID I",
 59   "I SHOULDN'T", "YOU SHOULDN'T",
 60   "YOU SHOULDN'T", "I SHOULDN'T",
 61   "I SHOULD", "YOU SHOULD",
 62   "YOU SHOULD", "I SHOULD",
 63   "I COULDN'T", "YOU COULDN'T",
 64   "YOU COULDN'T", "I COULDN'T",
 65   "I COULD", "YOU COULD",
 66   "YOU COULD", "I COULD"
 67 };
 68
 69 char *conjugationCombo[] = {
 70   "WHAT MAKES YOU BELIEVE ",
 71   "WOULD YOU BE PLEASED IF ",
 72   "ARE YOU SURE ",
 73   "I DON'T THINK ",
 74   "MAYBE YOU ARE RIGHT AND ",
 75   "HOW YOU CAME TO THE IDEA ",
 76   "I AM NOT SURE WHY YOU THINK ",
 77   "WHO TOLD YOU ",
 78   "PERHAPS IN YOUR FANTASIES ",
 79   "HOW DO YOU KNOW ",
 80   "DO YOU ENJOY ",
 81   "WHY YOU SAY ?",
 82   "I WAS NEVER TOLD ",
 83   "DOES IT PLEASE YOU TO BELIEVE ",
 84   "DO YOU REALLY BELIEVE YOURSELF ",
 85   "NEVER HEARED ",
 86   "DOES SOMEBODY ELSE ALSO BELIEVE ",
 87   "I THINK IT IS ONLY YOUR OPINION THAT ",
 88   "LAUGH OUT LOUD ",
 89   "OH, ",
 90   "WHAT, ",
 91   "HAVE YOU ASKED ANYONE ELSE AND THE ANSWER WAS "
 92 };
 93
 94 char *topics[] = {
 95   "ARE YOU INTERESTED IN ", "PHYSICS?",
 96   "DO YOU THINK I LIKE ", "CHEMISTRY?",
 97   "DO YOU LIKE ", "HISTORY?",
 98   "WHAT DO YOU THINK ABOUT ", "SPORT?",
 99   "DO YOU HAVE INTERESTS IN THE TOPIC ", "MOVIES?",
100   "WHAT DO YOU THINK ABOUT THE TOPIC ", "GOSSIP?",
101   "ANY INTERESTS IN ", "MATHEMATICS?",
102   "HAVE I MENTIONED THAT I LIKE ", "LANGUAGE ARTS?",
103   "WHAT COMES INTO YOUR MIND WHEN I SAY ", "ART GENERALLY?",
104   "DO YOU HAVE NEWS ABOUT ", "MUSEUMS?",
105   "I AM NOT SURE BUT DO YOU LIKE ", "POLITICS?",
106   "ANY THOUGHTS ABOUT ", "SOCIETY?",
107   "WHY DO I THINK YOU LIKE ", "PARTIES?",
108   "DOES ANY OF YOUR FRIENDS LIKE ", "TRAVELING?",
109   "DO WE LIKE ", "BIOLOGY?",
110   "WHAT ARE YOUR THOUGHTS ABOUT ", "GIRLS?",
111   "WHAT IS BETTER, GIRLS OR ", "BOYS?",
112   "WHAT IS BETTER, BOYS OR ", "CARS?",
113   "DO YOU THINK WE CAN IMPROVE OUR SOCIETY BY ", "BOOKS?",
114   "DO YOU ALSO SOMETIMES THINK NOTHING IS MORE BORING THAN ", "WATCHING TV?",
115   "DO YOU ALSO SOMETIMES THINK NOTHING IS MORE INTERESTING THAN ", "NATURE?",
116   "HAVE YOU READ A BOOK ABOUT ", "ANIMALS?",
117   "DO YOU WANT TO BRAINSTORM ABOUT ", "RELIGIONS?",
118   "WHAT IS WRONG TODAY WITH ", "OTHER COUNTRIES?",
119   "MAKE IT SENSE TO YOU TO TALK ABOUT ", "GETTING MARRIED?",
120   "NOWADAYS THE MAIN TOPIC SEEMS TO BE ", "SPACE TRAVEL?",
121   "DO YOU ALSO THINK EVERYBODY TALKS NOW ABOUT ", "ROBOTS?",
122   "DO YOU ALSO THINK NOTHING IS WORSE THAN ", "ARTIFICIAL INTELLIGENCE?",
123 };
124
125 char *keywords_responses[] = {
126   "NOTHING", "REALLY NOTHING?", "NOTHING AT ALL?", "I DO NOT BELIEVE",
127   "YES", "CAN YOU TELL ME MORE?", "I SEE", "OK!",
128   "MAYBE", "YOU ARE NOT SURE?", "I AM SURE", "YOU SEEM TO BE UNCERTAIN",
129   "FRIEND", "DO YOU HAVE MANY FRIENDS?", "WHAT'S THE NAME OF YOUR BEST FRIEND?", "I AM YOUR FRIEND",
130   "FAMILY", "DO YOU HAVE A BROTHER OR A SISTER?", "HOW IS YOUR DAD?", "HOW IS YOUR MOM",
131   "SORRY", "NO SORRY", "YOU DON'T NEED TO SAY SORRY", "NO PROBLEM",
132   "LOVE", "DO YOU LOVE SOMETHING MORE?", "I LOVE FRESH BATTERIES", "I FALL IN LOVE TO ANOTHER ROBOT GIRL",
133   "HATE", "NOT WORTHY TO HATE ANYTHING", "HATERS ARE HATERS", "WHEN YOU HATE, ONLY YOU SUFFER",
134   "BOY", "I AM A BOY", "DO YOU HAVE A BOYFRIEND?", "GIRLS ARE MORE CLEVER THAN BOYS",  
135   "GIRL", "I LIKE GIRLS", "ARE YOU A GIRL OR A BOY?", "GIRLS ARE CUTE",
136   "TIME", "YOU CAN CHECK MY TIME MODE IF YOU NEED TO KNOW THE TIME", "DON'T KNOW", "TIME FLIES",
137   "ROBOT", "I AM A ROBOT", "ROBOTS ARE COOL I THINK", "WHAT'S YOUR FAVORITE ROBOT?",
138   "NOT", "UM!", "WHY NOT?", "CAN YOU TELL ME THE REASON?",
139   "NO", "OH!", "ANY REASON?", "WHY?",  
140   "SOMETIMES", "HOW OFTEN IS SOMETIMES?", "UNDERSTAND", "I SEE",
141   "BOOK", "I LIKE TO READ BOOKS", "I LIKE TO READ PICTURE BOOKS", "YOU CAN LEARN MANY THINGS FROM BOOKS",
142   "MARRY", "I WANT TO GET MARRIED TO ANOTHER ROBOT LATER", "ARE YOU MARRIED?", "I HOPE YOU WILL HAVE A HAPPY LIFE",
143   "PARTY", "I LIKE TO DANCE AND TO SING", "I WANT TO GO TO A PARTY WITH YOU", "NO STANDING, ONLY DANCING",
144   "MOVIE", "I LIKE THE MOVIE FINDING NEMO", "I LIKE MICKEY MOUSE MOVIES", "I WANT TO WATCH A MOVIE WITH YOU",
145   "SPORT", "SPORT IS GOOD", "I LIKE TO PLAY BALL", "YOU DO SPORT?",
146   "CAR", "I like BMW", "I LIKE PORSCHE", "I LIKE FAST CARS VERY MUCH",
147   "ALSO", "OH, I DIDN'T EXPECT THAT", "NICE", "IT WAS MY GUESS",
148   "WHY", "I DON'T KNOW", "YOU MIGHT BETTER ASK A HUMAN NOT A ROBOT", "I HAVE NO ANSWER, SOORY",
149   "WHERE", "ON THE TABLE?", "ON THE CHAIR?", "MAYBE IN THE KITCHEN COUNTER",
150   "WHEN", "LAST WEEK I THINK", "NEXT WEEK I THINK", "TOMORROW?",
151   "WHAT", "REALLY NOT SURE", "CAN FIND NOTHING IN MY DATA BASE", "BETTER ASK A HUMAN"
152 };
153
154 char *user_repeat[] = {
155  "IT SEEMS YOU LIKE TO SAY: ", 
156  "I THINK ONE OF YOUR FAVORITE WORDS IS: ", 
157  "I HEAR QUITE OFTEN FROM YOU: ", 
158  "I FEEL BORING IF YOU ALWAYS USE: "
159 };
160
161 char *robot_repeat[] = {
162   "I DON'T LIKE TO REPEAT MYSELF BUT: ",
163   "I HAVE TO SAY IT AGAIN: ",
164   "AND AGAIN: ",
165   "I AM NOT GETTING TIRED TO SAY: "
166 };
167
168 char *user_delay[] = {
169   "HI! ARE YOU STILL HERE?",
170   "YOU FALL ASLEEP?",
171   "OUR CONVERSATION SEEMS VERY BORING FOR YOU",
172   "I DON'T WANT TO BE ALONE. PLEASE COME BACK"
173 };
174
175 char *daytimeGreeting[] = {
176   "GOOD MORNING. ", 
177   "GOOD AFTERNOON. ", 
178   "GOOD EVENING. "
179 };
180
181 long interval = 1000; 
182 long previousMillis = 0; 
183 int time_counter = 0;
184 boolean introduction_state = false;
185
186 void setup() {
187   Serial.begin(9600);
188   Entropy.Initialize(); // init random lib
189   RTC.begin(DateTime(__DATE__, __TIME__));
190 }
191
192 void loop() {
193   while (1) {
194     if (introduction_state == false) {
195       DateTime now = RTC.now();
196       if ((now.hour()*60 + now.minute()) >= 0 && (now.hour()*60 + now.minute()) <= 720) {
197         Serial.print(daytimeGreeting[0]);
198       }
199       if ((now.hour()*60 + now.minute()) >= 721 && (now.hour()*60 + now.minute()) <= 1020) {
200         Serial.print(daytimeGreeting[1]);
201       }
202       if ((now.hour()*60 + now.minute()) >= 1021) {
203         Serial.print(daytimeGreeting[2]);
204       }
205       Serial.println("MY NAME IS X2, I AM YOUR PERSONAL ROBOT. HOW ARE YOU? NICE TO CHAT WITH YOU. PLEASE ");
206       Serial.println("DON'T USE ANY PUNCTUATION MARKS BESIDE APOSTROPHE. SUBMIT THE WORD 'BYE' TO END THIS CONVERSATION");
207       Serial.println("");
208       introduction_state = true;
209     }
210     int size_conjugations = sizeof(conjugations)/sizeof(conjugations[0]);
211     int conjugationsPosition[size_conjugations / 2 + 1];
212     int size_conjugationCombo = sizeof(conjugationCombo)/sizeof(conjugationCombo[0]);
213     int size_topics = sizeof(topics)/sizeof(topics[0]);
214     int size_keywords_responses = sizeof(keywords_responses)/sizeof(keywords_responses[0]);
215     int size_user_repeat = sizeof(user_repeat)/sizeof(user_repeat[0]);
216     int size_robot_repeat = sizeof(robot_repeat)/sizeof(robot_repeat[0]);
217     int size_user_delay = sizeof(user_delay)/sizeof(user_delay[0]);
218     unsigned long currentMillis = millis();
219     if (currentMillis - previousMillis > interval) {
220       previousMillis = currentMillis;  
221       time_counter ++;
222     }
223     if (time_counter > 50) {
224       int randomNumber = Entropy.random(0, size_user_delay);
225       Serial.println(user_delay[randomNumber]);
226       Serial.println("");
227       time_counter = 0; // reset timer
228     }  
229     while (Serial.available() > 0) {
230       delay (2);
231       char ch = Serial.read();
232       User += ch;  
233     }
234     User.trim(); // remove leading and trailing whitespace
235     User.toUpperCase(); // force to upper case 
236     if (User.length() > 0) {
237       if (User.length() > 49) {
238         Serial.println("DATE INPUT TO0 LARGE TO BE PROCESSED");
239         Serial.println("");
240         time_counter = 0; 
241       } 
242       else if (LastInput == User) {
243         time_counter = 0; 
244         int randomNumber = Entropy.random(0, size_user_repeat);
245         Serial.print(user_repeat[randomNumber]);
246         Serial.println(User);
247         Serial.println("");
248       } 
249       else if (User == "BYE") {
250         Serial.println("BYE BYE. SEE YOU NEXT TIME");
251         Serial.println("");
252         User = "";
253         break;
254       } else {
255         Serial.println(User);
256         Serial.println("");
257         time_counter = 0; // reset timer
258         LastInput = User;
259         // cut and conjugate
260         int conjugationCounter = 0;
261         int conjugationPos = 0;
262         int keywordPos = 0;
263         boolean keywordFound = false;
264         String RobotOutput; 
265         for (keywordPos = 0; keywordPos < size_keywords_responses - 2; keywordPos = keywordPos + 4) { 
266           if (User.indexOf(keywords_responses[keywordPos]) != -1) {
267             int randomNumber = Entropy.random(keywordPos + 1, keywordPos + 4);
268             RobotOutput = keywords_responses[randomNumber];
269             keywordFound = true;
270             break;
271           }
272         }
273         if (keywordFound == false) {
274           for (conjugationPos = 0; conjugationPos < size_conjugations; conjugationPos = conjugationPos + 2) { 
275             if (User.lastIndexOf(conjugations[conjugationPos]) != -1) {
276               conjugationsPosition[conjugationCounter] = User.lastIndexOf(conjugations[conjugationPos]);
277               conjugationCounter ++;
278             }
279           }
280           if (conjugationCounter > 0) {
281             User = User.substring(maximum(conjugationsPosition, conjugationCounter));
282             for (conjugationPos = 0; conjugationPos < size_conjugations; conjugationPos = conjugationPos + 2) { 
283               if (User.indexOf(conjugations[conjugationPos]) != -1) {
284                 User.replace(conjugations[conjugationPos], conjugations[conjugationPos + 1]); 
285                 break;
286               }
287             }
288             if (User.length() == strlen(conjugations[conjugationPos + 1])) {
289               RobotOutput = User;
290               RobotOutput += '?';
291             } else {
292               int randomNumber = Entropy.random(0, size_conjugationCombo);
293               RobotOutput = conjugationCombo[randomNumber];
294               RobotOutput += User;
295               RobotOutput += '?';
296             }
297           } else {
298             // choose random topic
299             int randomNumber = Entropy.random(0, size_topics / 2);
300             randomNumber = randomNumber * 2;
301             int randomNumber2 = Entropy.random(0, size_topics / 2);
302             randomNumber2 = randomNumber2 * 2 + 1;
303             RobotOutput = topics[randomNumber];
304             RobotOutput += topics[randomNumber2];
305           }
306         }
307         if (LastOutput == RobotOutput) {
308           int randomNumber = Entropy.random(0, size_robot_repeat);
309           Serial.print(robot_repeat[randomNumber]);
310         }
311         int robot_think_delay = Entropy.random(500, 2001);
312         delay (robot_think_delay);
313         Serial.println(RobotOutput);
314         Serial.println("");
315         LastOutput = RobotOutput;
316       }
317       User = "";
318     }
319   }
320   while (1) {}
321 }

Chat snippet:

GOOD EVENING. MY NAME IS X2, I AM YOUR PERSONAL ROBOT. HOW ARE YOU? NICE TO CHAT WITH YOU. PLEASE DON'T USE ANY PUNCTUATION MARKS BESIDE APOSTROPHE. SUBMIT THE WORD 'BYE' TO END THIS CONVERSATION

HI

MAKE IT SENSE TO YOU TO TALK ABOUT TRAVELING?

NO

WHY?

TIME

TIME FLIES

I HAVE NO TIME

I AM NOT GETTING TIRED TO SAY: TIME FLIES

YES SEEMS SO

OK!

OK WHAT

BETTER ASK A HUMAN

YOU FALL ASLEEP?

 

Interesting resources:

Chatbot tutorial

Online JAVA version of ELIZA (mouse right click, view page source)

10 Tricks That Chatbots Use to Make You Believe They're Human

 

Videos

Video demonstration of x2's mode select. Example: Read temperature and map it to human sensations

Video x2 doing the math

Date & time demo

interacts with human beings


This is a companion discussion topic for the original entry at https://community.robotshop.com/robots/show/x2-humanoid-robot

Welcome back, Markus!

Welcome back, Markus!

Thanks, bdk6 and JerZ :slight_smile:

Thanks, bdk6 and JerZ :slight_smile:

Nice to see you back here!

Nice to see you back here! I’m always inspired by your work. Cool robot!

Well Markus, that picture

Well Markus, that picture does protect from bad comments :smiley:

However, nice to have you back. I know that your workout plan was quite extensive, so you may be excused to not build that much robots :slight_smile:

You need to improve your code for the motors. That guy is almost flipping over when stopping and going to reverse. But I know you know that already, so I just stay here and watch what you are going to do further.

Yeah I know lumi, so I

Yeah I know lumi, so I decreased the backward speed. Need to relocate the center of gravity, maybe using the arms.

**Interesting… **
Hey, the robot is looking awesome so far. Congratulations.
I was looking for a cheap manufacturing house. What is the name of the one you used?
Do you recommend it?

Sorry, they do not ship

Sorry, they do not ship outside China…

nice job

nice job

**Thanks any way **
Well, thanks any way :confused:
The PCBs are looking really good!

Just adding a list of common words is not a good idea…
Markov chains work from the principle that output text is created that is similar in ordering (but not exact) to the input text.

Therefore, inputting the transcript of a political speech will give you output that sort of looks like a political speech.

If you include after a short text the list of 100 most common English words, then the output will have sections that look like a list. Since the Markov chains include the ordering from the original text, the ordering of the list will be included.

I would love to see some of the class outputs when they used the script of the Godfather as input. :slight_smile:

You are right. In the

You are right. In the meantime I have written an ELIZA like code for x2, but I still want to implement Markov chains. I might record the user input for a while and start then Markov chaining.

I let play some of my employees with Cleverbot. The first thing everybody typed in was ‘time’, and they were all disappointed, cleverbot didn’t tell them the time, so this was one of the first functions I enabled in my chatbot code.

Instead of the Godfather I would prefer the script of Taxi Driver :slight_smile: