Beacon locating robot – Visualizing sensor readings with Processing by Christopher Hazlett
I've been boning up on my Processing skills, and have been fascinated by the library for it's "easy" integration with my Arduino projects. My beacon locating robot with the Arduino brain seemed like an excellent opportunity to wire up my robot to a Processing sketch and see what happens. There's not a lot to the application, but it proved very helpful in understanding the robot and how it was functioning.
There weren't too many gotchas with this application, but I'll point a couple of interesting tidbits:
1) I have the baud rate set at 38400, which is rather high, but I'm doing a number of different actions at the same time on the robot itself. A lower baud rate makes the robot stutter when it's sweeping the servo back and forth.
2) You do need to use the noLoop() method in the setup of the application. If you don't, the serial event and draw action will compete...or so it seems. I forgot to use the method once and the application locked up my computer. Just a warning.
3) If you're visualizing sensor data, the only way to get the nice rendered arc like you see in the video below, is to load an array every time you read in data. Then loop through that array and render all the previous readings as well as the most recently updated one. You also may have noticed that I'm not storing the servo angle in the angles[] array, just the readings. I can do this because the count of elements in the array matches the count of servo positions the robot iterates through as it sweeps. Given that, it's very easy to extrapolate what angle the readings are at by multiplying the position of the element in the array by the servo increment.
4) You'll also notice in the video below that when the sensor detects something, the drawn arc gets red and much longer. I could have normalized and reversed the readings so when it detected something, the bar got shorter so it more accurately represented an object. I tried that, and seen that done before, but I really liked how object detections jumped out instead of disappeared.
Obviously, a little more thought went into it, but that's about the only esoteric ideas/decisions you need to know if you want to do something similar.
The Code in Action
The Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 | // Serial Variables import processing.serial.*; Serial myPort; // The serial port: int baudRate = 38400; //make sure this matches the baud rate in the arduino program. int lf = 10; // Font Settings PFont font; float radianMultiplier; int[] angles; // Sensor Variables String direction = "N"; int sensorReading; int angle; int degreeIncrement = 6; int startAngle = 0; int endAngle = 180; int totalReadings = 0; void setup(){ smooth(); size(600, 400); //270 is Helvetica-Neue (my current favorite) to get a list use println(PFont.list()); font = createFont(PFont.list()[270], 24); textFont(font); radianMultiplier = PI / 180; totalReadings = (endAngle - startAngle)/degreeIncrement; angles = new int[totalReadings]; for (int i = 0; i < totalReadings; i++){ angles[i] = 0; } myPort = new Serial(this, Serial.list()[0], baudRate); myPort.bufferUntil(lf); noLoop(); } void draw() { background(#266014); renderClear(); renderScan(); renderDirection(); } void serialEvent(Serial p) { String inString; int pipeIndex = -1; int semicolonIndex = -1; String angleString; String sensorString; String dirString; String newString; String stepString; try { // the string is shaped like so: [angle]|[sensorReading];[direction] -- 6|450;N inString = (myPort.readString()); pipeIndex = inString.indexOf('|'); //find the pipe semicolonIndex = inString.indexOf(';'); //find the semicolon if (pipeIndex != -1) { //if we found the pipe angleString = inString.substring(0, pipeIndex); //parse angle reading sensorString = inString.substring(pipeIndex+1, semicolonIndex); dirString = inString.substring(semicolonIndex + 1, inString.length()-2); //length()-2 <- strips off the linefeed angle = int(angleString); sensorReading = int(sensorString); direction = dirString; angles[(angle/degreeIncrement) - 1] = sensorReading; } } catch(Exception e) { println(e); } redraw(); } // Render Functions void renderReadings(int angle, int sensor){ noStroke(); mediumFont(); text("Angle: " + angle, 139, 50); text("Sensor: " + sensor, 123, 90); } void renderScan(){ noStroke(); fill(#424242); rect(0,0,400,600); stroke(#000000); for (int x=0; x<totalReadings; x++) { boolean objectDetected = angles[x] >= 450; if(objectDetected == true){ fill(#980f0f, 400); }else{ fill(#ffffff, 100); } int angle = (x * degreeIncrement) - 180; noStroke(); arc(200, 325, angles[x], angles[x], radians(angle), radians(angle + 6)); if(objectDetected == true){renderAlert();} //renderReadings(x, angles[x]); } fill(#ffffff); rect(175,325,50,65); } void renderDirection(){ fill(#e38a20); rect(400,0,200,200); smallFont(); noStroke(); renderNorth(direction.equals("N")); renderSouth(direction.equals("S")); renderWest(direction.equals("W")); renderEast(direction.equals("E")); } void renderNorth(boolean isCurrent){ if(isCurrent == true){ fill(#2b2b2b); arc(500, 100, 175, 175, radians(225), radians(315)); }else{ fill(#696969, 475); arc(500, 100, 150, 150, radians(225), radians(315)); } fill(#ffffff); text("N", 492, 50); } void renderSouth(boolean isCurrent){ if(isCurrent==true){ fill(#2b2b2b); arc(500, 100, 175, 175, radians(45), radians(135)); }else{ fill(#696969, 475); arc(500, 100, 150, 150, radians(45), radians(135)); } fill(#ffffff); text("S", 492, 165); } void renderWest(boolean isCurrent){ if(isCurrent==true){ fill(#2b2b2b); arc(500, 100, 175, 175, radians(135), radians(225)); }else{ fill(#464646, 475); arc(500, 100, 150, 150, radians(135), radians(225)); } fill(#ffffff); text("W", 435, 110); } void renderEast(boolean isCurrent){ // the 405 angle here is weird, you'd think that because you're starting at 315 // it would be 45 (the beginning of the South arc), but // you have to continue around the circle adding angles // in when you pass the 360/0 degrees mark if(isCurrent==true){ fill(#2b2b2b); arc(500, 100, 175, 175, radians(315), radians(405)); }else{ fill(#464646, 475); arc(500, 100, 150, 150, radians(315), radians(405)); } fill(#ffffff); text("E", 553, 110); } void renderAlert(){ largeFont(); fill(#980f0f); rect(400,200,200,200); fill(#ffffff); text("ALERT", 440, 305); } void renderClear(){ mediumFont(); fill(#ffffff); text("CLEAR", 450, 305); } void smallFont(){ textFont(font, 24); } void mediumFont(){ textFont(font, 30); } void largeFont(){ textFont(font, 40); } |
Let me know if you have any questions, thoughts, or improvements.
- Chris