Robo Sapien Server
Robo Sapien Server
Robo Sapien Server
When I first got an Arduino, I was very intrigued by two separate projects: Karl Castleton'sRoboSapienIR 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.
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.
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. 2. 3. 4. 5. 6. // RoboSapienServer.cpp // RobosapienServer - Web enable a RoboSapien // Kevin N. Haw // http://www.KevinHaw.com/RoboSapienServer.php
// This project combines the default webserver example in the IDE distribution and Karl Castleton's(http://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 http://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. 298. 299. 300. 301. 302. 303. 304. 305. 306. 307. 308. 309. 310. 311. 312. 313. 314. 315. 316. 317. 318. 319. 320. 321. 322. 323. 324. 325. 326. 327. 328. 329. 330. 331. 332. 333. 334. 335. 336. 337. 338. 339. 340. 341.
SubmitButton(client, "RightArmUp", RSRightArmUp); SubmitButton(client, "RightArmIn", RSRightArmIn); SubmitButton(client, "RightArmOut", RSRightArmOut); SubmitButton(client, "RightArmDown", RSRightArmDown); SubmitButton(client, "RightArmSweep", RSRightHandSweep); client.println("<br>"); client.println("</td></tr></table>"); client.print("<br><br><br>URL Hex no: "); client.print(viRobsapienUrlCmd, HEX); client.println("<br />"); // send HTML footer HtmlFooter(client); // give the web browser time to receive the data delay(1); client.stop(); } } } // End while(connected) } } ////////////////////////////////////////////////////////////////// // Begin arduino entry points ////////////////////////////////////////////////////////////////// void setup() { // open the serial port at 9600 bps: Serial.begin(9600); // Print greeting Serial.println("Kevin's RobSapien Server"); RSSetup(); WebServerSetup(); } void loop() { RSLoop(); WebServerLoop();
342.
}
[Get Code]
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.
RoboSapienServer - The equivilent page at my personal website. RoboSapienIR - The arduino/robosapien project by Karl Castleton that inspired this project. robosapien.tk - Lots of robosapien mods, including specific notes on power and connectors. Discovery Science Center - Where I demoed this for Engineering Week.
:: The Background ::
Many of the WowWee robots are ideal for hacking. The RoboSapien Version 1.0 is very complex inside and simple at the same time. Doing a simple motor and switch count though yeilds 9 Motors and 8 Switches. Assuming you want both forward and reverse on each motor you would need on the order or 26 digital/analog IO pins. The Arduino
barebones from Modern Device has 13 digital and 6 analog but that is not enough to replace all this functionality. So a "Wedge" implementation seemed the best choice. The concept of the software "Wedge" has been around for a while. Basically it is a program that does nothing most of the time but then can be activated on a special sequence. Wedges have other names like hooks (as in Kernel Hooks), Terminate and Stay Resident, or call back. This approach allows me to still use the RoboSapien as normal but also take complete control.
Location of the head connector. Basically I powered the Barebones Arduino board off the VCC of the RoboSapien. This makes it so the Arduino goes on and
off with the Robosapien itself. VCC is the Red and the Black is ground on the head connector. The White wire is the IR from the IR module to theRoboSapien controller and is shunted through the Arduino.
:: The Wiring ::
I am working on some diagrams. Here is a verbal description.
Set USB/EXT Jumper to EXT Set J2 You need to solder an additional wire to the Red (VCC) wire on the head connector and run to the +5V on the Arduino. I just cut the wire and then soldered all three together. You need to solder an additional wire to the Black (GND) wire on the head connector and run to GND on the Arduino. You need to cut the White (IR) wire and run both ends to the Arduino The wire coming from the head itself needs to go to 2 on the Arduino. The rest of the wire (from the head connector) needs to go to 3 on the Arduino board. So I could see that everything was working I wired a diode and resistor from pin 10 to ground. A light comes on when the wedge is activated. That is it. Four wires and basically you can command the RoboSapien V1.0 to do anything it normally can do and more.
:: The Software ::
The heavy lifting is done by the RSReadCommand subroutine. This is the interrupt handler for when pin 2 rises. When it rises we wait 1 and 1/4 bit times and then check to see if the pin is high or low. We shift that bit into the RSCommand.
RSSendCommand simply sends the entire command to the Microcontroller in theRoboSapien. In the loop function when two RSStop commands are seen from the IR the wedge is activated and the RoboSapien microcontroller receives a number of commands in quick succession. They basically make him put his arms out and walk straight. If two RSStop bytes are received again normal remote control is restored. Wedge mode is a little sticky because the rapid succession of command after receiving the first stop have to take place before the second RSStop will be noticed. 1. 2. int IRIn = 2; // We will use an interrupt 3. int IROut= 3; // Where the echoed command will be sent from 4. 5. // Some but not all RS commands are defined 6. #define RSTurnRight 0x80 7. #define RSRightArmUp 0x81 8. #define RSRightArmOut 0x82 9. #define RSTiltBodyRight 0x83 10. #define RSRightArmDown 0x84 11. #define RSRightArmIn 0x85 12. #define RSWalkForward 0x86 13. #define RSWalkBackward 0x87 14. #define RSTurnLeft 0x88 15. #define RSLeftArmUp 0x89 16. #define RSLeftArmOut 0x8A 17. #define RSTiltBodyLeft 0x8B 18. #define RSLeftArmDown 0x8C 19. #define RSLeftArmIn 0x8D 20. #define RSStop 0x8E 21. #define RSWakeUp 0xB1 22. #define RSBurp 0xC2 23. #define RSRightHandStrike 0xC0 24. #define RSNoOp 0xEF 25. boolean RSEcho=true; // Should Arduino Echo RS commands
26. boolean RSUsed=true; // Has the last command been used 27. volatile int RSBit=9; // Total bits of data 28. volatile int RSCommand; // Single byte command from IR 29. int bitTime=516; // Bit time (Theoretically 833 but 516) 30. // works for transmission and is faster 31. int last; // Previous command from IR 32. 33. void setup() 34. { 35. pinMode(IRIn, INPUT); 36. pinMode(IROut, OUTPUT); 37. pinMode(10,OUTPUT); 38. digitalWrite(IROut,HIGH); 39. attachInterrupt(0,RSReadCommand,RISING); 40. last=RSNoOp; 41. } 42. 43. // Receive a bit at a time. 44. void RSReadCommand() { 45. delayMicroseconds(833+208); // about 1 1/4 bit times 46. int bit=digitalRead(IRIn); 47. if (RSBit==9) { // Must be start of new command 48. RSCommand=0; 49. RSBit=0; 50. RSUsed=true; 51. } 52. if (RSBit<8) { 53. RSCommand<<=1; 54. RSCommand|=bit; 55. } 56. RSBit++; 57. if (RSBit==9) RSUsed=false; 58. } 59. 60. // send the whole 8 bits 61. void RSSendCommand(int command) { 62. digitalWrite(IROut,LOW); 63. delayMicroseconds(8*bitTime); 64. for (int i=0;i<8;i++) { 65. digitalWrite(IROut,HIGH); 66. delayMicroseconds(bitTime); 67. if ((command & 128) !=0) delayMicroseconds(3*bitTime); 68. digitalWrite(IROut,LOW); 69. delayMicroseconds(bitTime); 70. command <<= 1;
71. } 72. digitalWrite(IROut,HIGH); 73. delay(250); // Give a 1/4 sec before next 74. } 75. 76. void loop() 77. { 78. if (!RSUsed) { 79. if (RSCommand==RSStop && last==RSStop) RSEcho=!RSEcho; 80. last=RSCommand; 81. if (!RSEcho){ 82. digitalWrite(10,HIGH); // Turn on LED let us know 83. // we have control our wedge 84. RSSendCommand(RSRightArmOut); 85. RSSendCommand(RSTiltBodyRight); 86. RSSendCommand(RSRightArmDown); 87. 88. RSSendCommand(RSLeftArmOut); 89. RSSendCommand(RSTiltBodyLeft); 90. RSSendCommand(RSLeftArmDown); 91. 92. RSSendCommand(RSWalkForward); 93. 94. } else { 95. digitalWrite(10,LOW); // No longer in control 96. RSSendCommand(RSCommand); 97. } 98. RSUsed=true; 99. } 100. } 101.
[Get Code]
One thing I noticed about my RoboSapien is that the left and right seemed to be confused. Or maybe WowWee assumed you were in front of the robot and I tend to stand in the back. So you could use this Stop-Stop wedge to flip right and left and then it would seem natural when following the robot.
:: What is next ::
All this is a lead up to an upgrade on a new RoboQuad. But this basic platform (even with breadboard) will allow me to experiment with different ways of sensing the environment and then have the RoboSapien autonomously navigate a house. I plan to try navigating to the brightest/darkest, noisiest/quitest, or warmest/coldest place with a single RoboSapienremote key press (after the two stop presses). Will add diagrams soon. (Ok. Not so soon.) Karl http://www.coloradomesa.edu/~kcastlet