Arduino Playground is read-only starting December 31st, 2018. For more info please look at this Forum Post

:: RoboSapienServer ::

When I first got an Arduino, I was very intrigued by two separate projects: Karl Castleton's RoboSapienIR and the basic WebServer that came with the Ethernet library. I decided to combine the two in order to make a web enabled robot. RoboSpaienServer is the result.

You can find a quick video tour here.


Basic Hardware & Tools

On the arduino side, you will need:

  • An arduino (obviously). Personally, I have an Arduino Mega, but you can use whatever configuration you prefer.
  • A compatible Ethernet shield. Since the Mega will not take the default shield without wiring modifications, I instead bought an Ethernet shield from NKC Electronics
  • A prototyping board and two resistors: one at 1 Kohm and another at 1.5K.

For the robot, I purchased a RoboSapien v1 toy robot on eBay.

To modify it for this project I used:

  • An old extension cord for a phone cable that had both male and female ends to it (an RJ14 to be exact). However, any connector (USB, etc) would have done, as long as it has four wires and you have male and female ends.
  • To enable use of the robot when not connected to the arduino, I also cannibalized an extra male connector that I could short out. This will be used to make a "stub plug."
  • Two header pin pairs, each 1 x 2 pins. While you can use straight wire instead, these hook up more easily into the protype board (above).
  • Some sort of insulation for the cut wires. While electrical tape or shrink wrap could be used, I recommend "liquid insulation," a rubber paint you can find in the electrical section of most hardware stores.

The tools required for this project are solder, a soldering iron, and lots of patience.


RoboSapien Wiring & Modifications

The basic principle of RoboSapienIR is that you replace the existing RoboSapien infrared remote with the Arduino, sending electical impulses in lieu of the IR impulses from the remote. This is done by tapping into the connector between the motherboard and the robot's head where the IR sensor resides. There's a detailed schematic below, but this section will take you step by step through the process.

I began by taking my RJ14 cable and cutting it, making sure I had at least six inches on each end. I then removed the robot's back shell and used a soldering iron to melt a hole in the left side, symetrical with the power switch on the right. You can then thread the cable with the female connector through the hole in the plastic casing. The two photos below demonstrate what these modifications look like when the project is done (the gray wire taped to the cable on the right photo is the "stub plug" to allow operation without the arduino).

There are three wires of interest in the connector from the head to the motherboard: a 3.3V power (red), a ground (black), and the infrared signal (white). With the cable threaded through the hole in the backshell (you can't insert it afterwards!), I connected the red and black wires to the same colored wires in my cable by burning away the insulation with a soldering iron and then soldering my cable's wires directly to the undamaged wires. However, I have since discovered at this site that you could instead pull out the motherboard, flip it over, and solder directly to solder pads. If you have an opportunity to do this, I'd recommend it as the wires on the connectors are relatively fragile and easy to break.

To connect to the IR signal, cut the white wire and strip some insulation from either end. I then soldered my new cable's green wire to the end running to the robot's head and the yellow wire to the end heading towards the motherboard. After that, I insulated all the exposed connections with rubber "liquid insulation" paint (the white stuff near the connector in the righthand photo above).

For the male end of the cable, I soldered the red and black pins to a 1 x 2 pin header and the yellow and green wires to another 1 x 2 header. For the "stub plug," I took the spare male connector and soldered together the green and yellow wires. To test your wiring, put the "stub plug" into the female connector off the back of the robot and power up the robot. This should restore the wiring to its original state and allow you to use the remote to command the robot. If this works, insulate all exposed wires and button up the robot by replacing the backplate.

Note that the red power wire and the green wire back to the robot's IR sensor are unused in this project. I brought them out anyways for future projects.


Arduino Wiring

When I first tried the RoboSapienIR hack, I had a lot of difficulties. I discovered here that while the arduino uses 5V for signals, the robosapien uses 3.3V. The subtlety is that if your ground wiring is poor the hack will occasionally work! To make it work properly, you will need to construct a voltage divider to convert the 5V signal to 3.3V. Below is a schematic showing all the connections (click to enlarge) on both ends of the cable.

In addition to the above, I also cut a piece of plexiglass to fit the bottom of my Mega, drilled holes to match those in the Mega, and secured the two together with zip ties. This insulates the arduino and allows me to secure it in turn to the protoype board with more zip ties. You can see in the lefthand photo above that this makes a much more compact arrangement than having the whole thing lying around.


Arduino Operation

You can now use the default RoboSapienIR sketch if you'd like. However, to make the web server work, download the source code below and cut and paste it into a new sketch. Be sure to modify the ip[] array (line 21) to reflect an IP address that will work on your local LAN.

  1. // RoboSapienServer.cpp
  2. // RobosapienServer - Web enable a RoboSapien
  3. // Kevin N. Haw
  4. // https://www.KevinHaw.com/RoboSapienServer.php
  5.  
  6. // This project combines the default webserver example in the IDE distribution and Karl Castleton's(https://home.mesastate.edu/~kcastlet) RoboSapienIR (http://playground.arduino.cc/Main/RoboSapienIR).
  7. // Source code merged from those two sources.
  8.  
  9. // Include files
  10. #include <Ethernet.h>
  11. #include <string.h>
  12.  
  13. //////////////////////////////////////////////////////////////////
  14. // Begin Web Server specific variable deinitions
  15. //////////////////////////////////////////////////////////////////
  16.  
  17. byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
  18.  
  19. // KNH, 02/09/2010 - Change IP address to use local subnet at home
  20. //byte ip[] = { 10, 0, 0, 177 };
  21. byte ip[] = { 192, 168, 1, 177 };
  22.  
  23. // Server for web requests
  24. Server server(80);
  25.  
  26. // Define field name in the submitted form
  27. #define SUBMIT_BUTTON_FIELDNAME "RSCmd"
  28.  
  29. // String for HTTP request variables
  30. char pcHttpReqRsCmd[20] = {'\0'};
  31.  
  32.  
  33. volatile int viRobsapienUrlCmd = -1;  // A robosapien command sent over the URL of a webpage HTTP request
  34.  
  35.  
  36. //////////////////////////////////////////////////////////////////
  37. // Begin Robosapien specific variable deinitions
  38. //////////////////////////////////////////////////////////////////
  39.  
  40.  
  41. // Some but not all RS commands are defined
  42. #define RSTurnRight       0x80
  43. #define RSRightArmUp      0x81
  44. #define RSRightArmOut     0x82
  45. #define RSTiltBodyRight   0x83
  46. #define RSRightArmDown    0x84
  47. #define RSRightArmIn      0x85
  48. #define RSWalkForward     0x86
  49. #define RSWalkBackward    0x87
  50. #define RSTurnLeft        0x88
  51. #define RSLeftArmUp       0x89
  52. #define RSLeftArmOut      0x8A
  53. #define RSTiltBodyLeft    0x8B
  54. #define RSLeftArmDown     0x8C
  55. #define RSLeftArmIn       0x8D
  56. #define RSStop            0x8E
  57. #define RSWakeUp          0xB1
  58. #define RSBurp            0xC2
  59. #define RSRightHandStrike 0xC0
  60. #define RSNoOp            0xEF
  61.  
  62. // Subset of additional codes pulled from https://www.contrib.andrew.cmu.edu/~ebuehl/robosapien-lirc/ir_codes.htm
  63. #define RSRightHandSweep  0xC1
  64. #define RSRightHandStrike2 0xC3
  65. #define RSHigh5           0xC4
  66. #define RSFart            0xC7
  67. #define RSLeftHandStrike  0xC8
  68. #define RSLeftHandSweep  0xC9
  69.  
  70. #define RSWhistle         0xCA
  71. #define RSRoar            0xCE
  72.  
  73.  
  74. int IRIn = 2;            // We will use an interrupt
  75. int IROut= 3;            // Where the echoed command will be sent from
  76.  
  77.  
  78. boolean RSEcho=true;      // Should Arduino Echo RS commands
  79. boolean RSUsed=true;      // Has the last command been used
  80. volatile int RSBit=9;     // Total bits of data
  81. volatile int RSCommand;   // Single byte command from IR
  82. int bitTime=516;          // Bit time (Theoretically 833 but 516)
  83.                           // works for transmission and is faster
  84. int last;                 // Previous command from IR
  85.  
  86.  
  87.  
  88.  
  89.  
  90. //////////////////////////////////////////////////////////////////
  91. // Begin Robosapien specific code
  92. //////////////////////////////////////////////////////////////////
  93.  
  94.  
  95.  
  96. // Receive a bit at a time.
  97. //  NOTE: Unused in the RoboServer aplication
  98. void RSReadCommand() {
  99.   delayMicroseconds(833+208);  // about 1 1/4 bit times
  100.   int bit=digitalRead(IRIn);
  101.   if (RSBit==9) { // Must be start of new command
  102.     RSCommand=0;
  103.     RSBit=0;
  104.     RSUsed=true;
  105.   }
  106.   if (RSBit<8) {
  107.     RSCommand<<=1;
  108.     RSCommand|=bit;
  109.   }
  110.   RSBit++;
  111.   if (RSBit==9) RSUsed=false;
  112. }
  113.  
  114. // send the whole 8 bits
  115. void RSSendCommand(int command) {
  116.   digitalWrite(IROut,LOW);
  117.   delayMicroseconds(8*bitTime);
  118.   for (int i=0;i<8;i++) {
  119.     digitalWrite(IROut,HIGH);  
  120.     delayMicroseconds(bitTime);
  121.     if ((command & 128) !=0) delayMicroseconds(3*bitTime);
  122.     digitalWrite(IROut,LOW);
  123.     delayMicroseconds(bitTime);
  124.     command <<= 1;
  125.   }
  126.   digitalWrite(IROut,HIGH);
  127.   delay(250); // Give a 1/4 sec before next
  128. }
  129.  
  130.  
  131. // Set up RoboSpapien functionality
  132. void RSSetup()                    
  133. {
  134.   pinMode(IRIn, INPUT);    
  135.   pinMode(IROut, OUTPUT);
  136.   pinMode(10,OUTPUT);
  137.   digitalWrite(IROut,HIGH);
  138.  
  139.   attachInterrupt(0,RSReadCommand,RISING);
  140.  
  141.   last=RSNoOp;
  142.  
  143.   // Make robot burp to indicate setup is complete
  144.   RSSendCommand(RSBurp);
  145.  
  146. }
  147.  
  148.  
  149. // Loop for RoboSapien functionality
  150. // Write only functionality - send the command from the web page to the robot, ignoring any input from the remote
  151. void RSLoop()
  152. {
  153.   // Has a new command come in from the server?
  154.   if(viRobsapienUrlCmd != -1)
  155.     {
  156.     // New command - send it to robot
  157.     Serial.print("Sending command to RoboSapien: ");
  158.     Serial.println(viRobsapienUrlCmd, HEX);
  159.     RSSendCommand(viRobsapienUrlCmd);
  160.  
  161.     // Now clear command
  162.     viRobsapienUrlCmd = -1;
  163.     }
  164. }
  165.  
  166. //////////////////////////////////////////////////////////////////
  167. // Begin Webserver Specific Code
  168. //////////////////////////////////////////////////////////////////
  169.  
  170. // Print ourt MIME and HTML header at top of webpage
  171. void HtmlHeader(Client client)
  172.   {
  173.   client.println("HTTP/1.1 200 OK");
  174.   client.println("Content-Type: text/html");
  175.   client.println();
  176.   client.println("<HTML>\n<HEAD>");
  177.   client.println("  <TITLE>Kevin's Arduino Webserver</TITLE>");//
  178. //  client.println("  <META HTTP-EQUIV=\"refresh\" CONTENT=\"5\">");
  179.   client.println("</HEAD><BODY bgcolor=\"#9bbad6\">");
  180.   }
  181.  
  182. // Print the footer at the bottom of the webpage
  183. void HtmlFooter(Client client)
  184.   {
  185.   client.println("</BODY></HTML>");
  186.   }
  187.  
  188. // Print a submit button with the indicated label wrapped in a form for the indicated hex command
  189. void SubmitButton(Client &client, char *pcLabel, int iCmd)
  190.   {
  191.   client.print("<form method=post action=\"/?");
  192.   client.print(iCmd, HEX);
  193.   client.print("\"><input type=submit value=\"");
  194.   client.print(pcLabel);
  195.   client.print("\" name=\"" SUBMIT_BUTTON_FIELDNAME "\">");
  196.   client.println("</form>");  
  197.   }
  198.  
  199. // Parse an HTTP request header one character at a time, seeking string variables
  200. void ParseHttpHeader(Client &client)
  201.   {
  202.   char c;
  203.  
  204.   // Skip through until we hit a question mark (first one)
  205.   while((c = client.read()) != '?' && client.available())
  206.     {
  207.     // Debug - print data
  208.     Serial.print(c);
  209.     }
  210.  
  211.   // Are we here for a question mark or did we run out of data?
  212.   if(client.available() > 2)
  213.     {
  214.     char pcUrlNum[3], *pc;
  215.  
  216.     // We have enough data for a hex number - read it
  217.     for(int i=0; i < 2; i++)
  218.       {
  219.       // Read and dump data to debug port
  220.       Serial.print(c = pcUrlNum[i] = client.read());
  221.       }
  222.     // Null terminate string
  223.     pcUrlNum[2] = '\0';
  224.  
  225.     // Get hex number
  226.     viRobsapienUrlCmd = strtol(pcUrlNum, &pc, 0x10);  
  227.     }
  228.  
  229.   // Skip through and discard all remaining data
  230.   while(client.available())
  231.     {
  232.     // Debug - print data
  233.     Serial.print(c = client.read());
  234.     }
  235.   }
  236.  
  237. // Set up webserver functionality
  238. void WebServerSetup()
  239. {
  240.   Ethernet.begin(mac, ip);
  241.   server.begin();
  242. }
  243.  
  244. // Web server loop  
  245. void WebServerLoop()
  246. {  
  247.   Client client = server.available();
  248.   boolean bPendingHttpResponse = false; // True when we've received a whole HTTP request and need to output the webpage
  249.   char c;  // For reading in HTTP request one character at a time
  250.  
  251.   if (client) {
  252.     // Loop as long as there's a connection
  253.     while (client.connected()) {
  254.       // Do we have pending data (an HTTP request)?    
  255.       if (client.available()) {
  256.  
  257.         // Indicate we need to respond to the HTTP request as soon as we're done processing it
  258.         bPendingHttpResponse = true;
  259.  
  260.         ParseHttpHeader(client);        
  261.         }
  262.       else
  263.         {
  264.         // There's no data waiting to be read in on the client socket.  Do we have a pending HTTP request?
  265.         if(bPendingHttpResponse)
  266.           {
  267.           // Yes, we have a pending request.  Clear the flag and then send the webpage to the client
  268.           bPendingHttpResponse = false;
  269.  
  270.           // send a standard http response header and HTML header
  271.           HtmlHeader(client);
  272.  
  273.           // Put out a text header
  274.           client.println("<H1>Kevin's RoboSapien Webserver</H1>");          
  275.  
  276.           client.println("<table border cellspacing=0 cellpadding=5><tr>");
  277.           client.println("<td>");
  278.  
  279.           // Create buttons
  280.           SubmitButton(client, "WakeUp", RSWakeUp);          
  281.           SubmitButton(client, "Roar", RSRoar);          
  282.           SubmitButton(client, "Whistle", RSWhistle);          
  283.           SubmitButton(client, "High5", RSHigh5);    
  284.           client.println("<br>");          
  285.  
  286.           client.println("</td><td>");
  287.  
  288.           SubmitButton(client, "LeftArmUp", RSLeftArmUp);
  289.           SubmitButton(client, "LeftArmIn", RSLeftArmIn);
  290.           SubmitButton(client, "LeftArmOut", RSLeftArmOut);
  291.           SubmitButton(client, "LeftArmDown", RSLeftArmDown);
  292.           SubmitButton(client, "LeftArmSweep", RSLeftHandSweep);
  293.           client.println("<br>");          
  294.  
  295.           client.println("</td><td>");
  296.  
  297.           SubmitButton(client, "RightArmUp", RSRightArmUp);
  298.           SubmitButton(client, "RightArmIn", RSRightArmIn);
  299.           SubmitButton(client, "RightArmOut", RSRightArmOut);
  300.           SubmitButton(client, "RightArmDown", RSRightArmDown);
  301.           SubmitButton(client, "RightArmSweep", RSRightHandSweep);
  302.           client.println("<br>");          
  303.  
  304.           client.println("</td></tr></table>");
  305.  
  306.           client.print("<br><br><br>URL Hex no: ");
  307.           client.print(viRobsapienUrlCmd, HEX);
  308.           client.println("<br />");
  309.  
  310.           // send HTML footer
  311.           HtmlFooter(client);
  312.  
  313.           // give the web browser time to receive the data
  314.           delay(1);
  315.           client.stop();
  316.           }
  317.         }
  318.       }  // End while(connected)
  319.   }
  320. }
  321.  
  322. //////////////////////////////////////////////////////////////////
  323. // Begin arduino entry points
  324. //////////////////////////////////////////////////////////////////
  325.  
  326. void setup()
  327. {
  328.   // open the serial port at 9600 bps:
  329.   Serial.begin(9600);
  330.  
  331.   // Print greeting
  332.   Serial.println("Kevin's RobSapien Server");
  333.  
  334.   RSSetup();
  335.   WebServerSetup();
  336. }
  337.  
  338. void loop()
  339. {
  340.   RSLoop();
  341.   WebServerLoop();
  342. }


Operation

Power up the robot. When you upload the sketch to your arduino, you should hear the robot "burp" when the code runs. This is the signal from the software indicating that it is now in control of the robot.

This will also occur every time you reset the board.

You should then be able to connect your Ethernet shield to the local LAN and log into the webserver. You may have to reset the board after you cable it up. Once you log in, you should see something like the screenshot below in your browser.

You can now press buttons to move the robot's arms, make him roar or high 5, etc. If the robot is inactive for a period of time, it will yawn and "go to sleep" to enter a power saving mode. Press the "Wake Up" button to get it going.


Modifications

The web server works by appending a question mark and a hexidecimal number to the back of the URL to send a command code to the robot (i.e. "?B1" to wake up). You can easily add more buttons or change the codes passed to the SubmitButton() call in lines 280 to 302 (I intentionally left out any movement commands because I didn't want the robot walking off the demo table I had set up for it).

You can modify the background color or make any modifications you might put on a normal webpage by playing with the HTML sent over the socket connection. Just make it reflect your own sense of aesthetics. Also, I learned from my demo at the Discovery Science Center that the buttons should be bigger if you want passersby to use the web page. You should also change the name of the webpage to remove the "Kevin's Webpage" titles at line 274 and 177. After all, it's your webserver now!

Additional mods to the actual robosapien operation are also available. You could modify the RSLoop() (lines 151-164) to read the IR sensor via the RSReadCommand() routine so that either the web server or the remote can be used to control the robot. You cold also program a long series of commands (e.g. move ten steps forward, then ten back) or add programming commands. Also, if you don't like the "burp" to signal the arduino has taken control, change it (line 144).

Finally, replacing the Ethernet shield with a WiFi module or leaving the server up and connected to the Internet so anyone can manipulate it (say, with a webcam trained on your robot) would be interesting directions to take this.


Final Thoughts

This was a pretty fun project for me and did not take all that much time to get running. In fact, it has taken me longer to document the whole thing in this writeup than it did to do in the first place.

If you have any questions or comments, please reach me via the "Contact" link at my website.

Thanks!


Links

Useful links for this project.