<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Blair Kelly</title>
	<atom:link href="http://www.blairkelly.ca/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.blairkelly.ca</link>
	<description>Multimedia, Design, Communications</description>
	<lastBuildDate>Wed, 16 May 2012 18:51:47 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3</generator>
		<item>
		<title>Controller Maps</title>
		<link>http://www.blairkelly.ca/2012/04/24/controller-maps/</link>
		<comments>http://www.blairkelly.ca/2012/04/24/controller-maps/#comments</comments>
		<pubDate>Tue, 24 Apr 2012 16:13:30 +0000</pubDate>
		<dc:creator>blair</dc:creator>
				<category><![CDATA[Arduino]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[DIY]]></category>
		<category><![CDATA[Multimedia]]></category>
		<category><![CDATA[360]]></category>
		<category><![CDATA[arduino]]></category>
		<category><![CDATA[Controller]]></category>
		<category><![CDATA[F510]]></category>
		<category><![CDATA[G27]]></category>
		<category><![CDATA[Logitech]]></category>
		<category><![CDATA[Maps]]></category>
		<category><![CDATA[Mini]]></category>
		<category><![CDATA[PS3]]></category>
		<category><![CDATA[Steering]]></category>
		<category><![CDATA[Wheel]]></category>
		<category><![CDATA[Wifly]]></category>
		<category><![CDATA[XBOX]]></category>

		<guid isPermaLink="false">http://www.blairkelly.ca/?p=3023</guid>
		<description><![CDATA[Controller maps for PS3, Xbox360, G27 Steering Wheel and Logitech F510 Rumblepad when using Processing's ProControll Library.]]></description>
			<content:encoded><![CDATA[<p>After putting together the first phase of my <a href="http://www.blairkelly.ca/2012/04/20/arduino-wifly-mini/">Arduino Wifly Mini</a>, I realized I had a bunch of controller maps people might find useful if using controllers with Processing&#8217;s ProControll library. I&#8217;m not certain these maps will be valid across all Macs or PCs, but here they are anyway in case you find them helpful. They might even apply when using other IDEs or libraries &#8211; but I&#8217;m not sure.</p>
<p><br/><br />
<b>PS3 Controller, on Mac OS X</b></p>
<p>Button Number, Button:</p>
<p>0: Select<br />
1: Left Joystick Downpress<br />
2: Right Joystick Downpress<br />
3: Start<br />
4: Up-Arrow, left keypad.<br />
5: Right-Arrow, left keypad.<br />
6: Down-Arrow, left keypad.<br />
7: Left-Arrow, left keypad.<br />
8: Left paddle<br />
9: Right paddle<br />
10: Left trigger button<br />
11: Right Trigger Button<br />
12: Triangle<br />
13: Circle<br />
14: X<br />
15: Square<br />
16: PlayStation Button<br />
17, 18 don&#8217;t appear activated.</p>
<p>Slider Number, Slider:</p>
<p>0: Left Joystick Left-And-Right (X axis)<br />
1: Left Joystick Up-and-Down (Y axis)<br />
2: Right Joystick Left-and-Right (X axis)<br />
3: Right Joystick Up-and-Down (Y axis)</p>
<p><br/><br />
<b>Xbox360 Controller, Windows 7</b></p>
<p>Button Number, Button:</p>
<p>0: A<br />
1: B<br />
2: X<br />
3: Y<br />
4: LB<br />
5: RB<br />
6: Select, Back<br />
7: Start<br />
8: Left Joystick Down<br />
9: Right Joystick Down<br />
10: Cooliehat (true cooliehat).</p>
<p>Slider Number, Slider:</p>
<p>0: Left Joystick Y axis (Up and Down)<br />
1: Left Joystick X axis (Left and Right)<br />
2: Right Joystick Y axis (Up and Down)<br />
3: Right Joystick X axis (Left and Right)<br />
4: Right and Left Paddles; input sits at 0, when right paddle is pressed it drops to -1, when left paddle is pressed it goes up to 1, when both are pressed at the same time the input reading is 0.</p>
<p><br/><br />
<b>Xbox 360 Controller, on Mac OS X</b></p>
<p>0: Cooliehat UP<br />
1: Cooliehat DOWN<br />
2: Cooliehat LEFT<br />
3: Cooliehat RIGHT<br />
4: START<br />
5: SELECT<br />
6: Left Joystick Down<br />
7: Right Joystick Down<br />
8: LB<br />
9: RB<br />
10: XBOX Light Button<br />
11: A<br />
12: B<br />
13: X<br />
14: Y</p>
<p>Slider Number, Slider:</p>
<p>0: Left Joystick X axis (Left and Right)<br />
1: Left Joystick Y Axis (Up and Down)<br />
2: Right Joystick X axis (Left and Right)<br />
3: Right Joystick Y axis (Up and Down)<br />
4: Left Paddle<br />
5: Right Paddle</p>
<p><br/><br />
<b>Logitech G27 Steering Wheel, Windows 7</b></p>
<p>Button Number, Button:</p>
<p>0: Cooliehat (true cooliehat).<br />
1: Red button 1 on stickshift panel.<br />
2: Red button 2 on stickshift panel.<br />
3: Red button 3 on stickshift panel.<br />
4: Red button 4 on stickshift panel.<br />
5: Right Paddle behind Wheel<br />
6: Left Paddle Behind wheel<br />
7: On wheel, Right side, Red Button 1 (top).<br />
8: On wheel, Left Side, Red Button 1 (top).<br />
9: 1st gear<br />
10: 2nd gear<br />
11: 3rd gear<br />
12: 4th gear<br />
13: 5th gear<br />
14: 6th gear<br />
15: Reverse gear<br />
16: POV TOP<br />
17: POV RIGHT<br />
18: POV BOTTOM<br />
19: POV LEFT<br />
20: On wheel, Right side, Red Button 2 (middle)<br />
21: On wheel, Left side, Red Button 2 (middle)<br />
22: On wheel, right side, Red Button 3 (bottom)<br />
23: On wheel, left side, Red Button 3 (bottom)</p>
<p>Slider Number, slider:</p>
<p>0: Wheel<br />
1: Combined pedals<br />
2: Brake<br />
3: Accelerator<br />
4: Clutch</p>
<p><br/><br />
<b>Logitech F510 Rumblepad, Windows 7, XInput Mode</b></p>
<p>Button Number, Button:</p>
<p>0: A<br />
1: B<br />
2: X<br />
3: Y<br />
4: Left Trigger<br />
5: Right Trigger<br />
6: Back<br />
7: Start<br />
8: Left Joystick Depressed<br />
9: Right Joystick Depressed<br />
10: Cooliehat (true cooliehat).</p>
<p>Slider Number, Slider:</p>
<p>0: Left Joystick, Y axis (Up and Down)<br />
1: Left Joystick, X axis (Left and Right)<br />
2: Right Joystick Y axis (Up and Down)<br />
3: Right Joystick X axis (Left and Right)<br />
4: Left and Right paddles, combined.</p>
<p><br/><br />
<b>Logitech F510 Rumblepad, Windows 7, DirectInput Mode</b></p>
<p>0: Cooliehat (true cooliehat)<br />
1: X<br />
2: A<br />
3: B<br />
4: Y<br />
5: Left Trigger<br />
6: Right Trigger<br />
7: Left Paddle<br />
8: Right paddle<br />
9: Back<br />
10: Start<br />
11: Left Joystick Down<br />
12: Right Joystick Down</p>
<p>Slider Number, Slider:</p>
<p>0: Right Joystick, Y axis (Up and Down)<br />
1: Right Joystick, X axis (Left and Right)<br />
2: Left Joystick Y axis (Up and Down)<br />
3: Left Joystick X axis (Left and Right)</p>
]]></content:encoded>
			<wfw:commentRss>http://www.blairkelly.ca/2012/04/24/controller-maps/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Arduino Wifly Mini</title>
		<link>http://www.blairkelly.ca/2012/04/20/arduino-wifly-mini/</link>
		<comments>http://www.blairkelly.ca/2012/04/20/arduino-wifly-mini/#comments</comments>
		<pubDate>Fri, 20 Apr 2012 19:31:10 +0000</pubDate>
		<dc:creator>blair</dc:creator>
				<category><![CDATA[Arduino]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Featured]]></category>
		<category><![CDATA[Multimedia]]></category>
		<category><![CDATA[Radio]]></category>
		<category><![CDATA[Technology]]></category>
		<category><![CDATA[Adruino]]></category>
		<category><![CDATA[FPV]]></category>
		<category><![CDATA[Mini]]></category>
		<category><![CDATA[ROV]]></category>
		<category><![CDATA[WiFi]]></category>
		<category><![CDATA[Wifly]]></category>

		<guid isPermaLink="false">http://www.blairkelly.ca/?p=2715</guid>
		<description><![CDATA[The Arduino Wifly Mini is controlled with a PS3 or XBOX360 controller, or with a Logitech G27 steering wheel. It features a first person view and force feedback. ]]></description>
			<content:encoded><![CDATA[<p><i>This has been a rather large project. This post is still being expanded with explanations and notes.</i></p>
<div id="toc-holder">
<div id="toc">
<b>Contents</b></p>
<p><a href="#awm-intro">Introduction</a><br />
<a href="#awm-parts">Parts List</a><br />
<a href="#awm-cav">Choosing a Vehicle</a><br />
<a href="#awm-moc">Means of Communication</a><br />
<span class="inset1"><a href="#awm-gcom">General Communication</a></span><br />
<span class="inset1"><a href="#awm-vtx">Video Transmission</a></span><br />
<a href="#awm-ffs">Force Feedback Sensors</a><br />
<span class="inset1"><a href="#awm-pot">Potentiometer</a></span><br />
<span class="inset1"><a href="#awm-acc">Accelerometer</a></span><br />
<a href="#awm-mfwm">More Features Worth Mentioning</a><br />
<span class="inset1"><a href="#awm-escswitch">The ESC Switch</a></span><br />
<span class="inset1"><a href="#awm-cps">The Camera-Panning Servo</a></span><br />
<a href="#awm-nwc">A Note on Wifly Configuration</a><br />
<a href="#awm-code">The Code</a><br />
<span class="inset1"><a href="#awm-codepermissions">Permissions</a></span><br />
<span class="inset1"><a href="#awm-codelibs">Libraries</a></span><br />
<span class="inset1"><a href="#awm-acode">Arduino Code</a></span><br />
<span><span class="inset2"><a href="#awm-spftaw">Sending Packets from the Arduino Wifly</a></span></span><br />
<span class="inset1"><a href="#awm-pcode">Processing Code</a></span><br />
<span><span class="inset2"><a href="#awm-udppl">UDP Packet Listening</a></span></span><br />
<span><span class="inset2"><a href="#awm-cme">Controller-Map Example</a></span></span><br />
<a href="#awm-nai">Notes and Ideas</a><br />
<span class="inset1"><a href="#awm-overall">Overall Performance</a></span><br />
<span class="inset1"><a href="#awm-bulk">Bulk</a></span><br />
<span class="inset1"><a href="#awm-range">Range</a></span><br />
<span class="inset1"><a href="#awm-changefrequencies">Changing Frequencies</a></span><br />
<span class="inset1"><a href="#awm-platformsize">Platform Size</a></span><br />
<span class="inset1"><a href="#awm-cellcom">Communication Through Cellular Broadband</a></span><br />
<a href="#awm-uh">Update History</a>
</div>
</div>
<h2 id="awm-intro">Introduction</h2>
<p><iframe width="560" height="315" src="http://www.youtube.com/embed/lSnqq6OPn8A" frameborder="0" allowfullscreen></iframe></p>
<p>Since I was a kid I&#8217;ve been captivated by remote control vehicles. The R/C cars of <a href="http://www.toysrus.ca/" target="_blank">Toys&#8217;R'Us</a> can be fun, but what I&#8217;ve always been enthralled by are remotely operated vehicles (ROVs) such as the kind used to explore sunken ships. Or the type police forces use to disarm or explode a bomb. In my early teens I made my first attempt at an ROV by modifying a toy R/C car and mounting a small camera in its body. Technology has come a long way since then.</p>
<p>A few months ago a friend of mine reminded me of <a href="http://arduino.cc" target="_blank">Arduino</a>. I was in university when I first heard of it, but had been under the impression it was a special device musicians employed to make peculiar noises when sticks were bashed on dinner-plates. My friend told me Arduino was much more than that. He showed me Matt Richardson&#8217;s &#8220;<a href="http://blog.makezine.com/2011/08/16/enough-already-the-arduino-solution-to-overexposed-celebs/" target="_blank">Enough Already</a>,&#8221; and that&#8217;s when I got an inkling of Arduino&#8217;s potential.</p>
<p>I picked up an Arduino UNO and began experimenting. At first I got a feel for the platform and <a href="http://www.blairkelly.ca/2011/11/20/high-speed-photography-first-attempt/" target="_blank">attempted</a> a modified version of Richardson&#8217;s &#8220;<a href="http://www.youtube.com/watch?v=R8_dAgaBBdI" target="_blank">Arduino High Speed Photography Trigger</a>.&#8221; Naturally, this tinkering evolved into a desire to build an ROV operated by a PS3 or XBOX 360 controller, or a Logitech G27 steering wheel, with communication done over WiFi, and with a First-Person-View (FPV), and Force Feedback.</p>
<p>The Arduino and FPV R/C communities are vast and full of extraordinarily talented people. So it was no surprise, once I had begun investigating the viability of my project, to discover that others had already done similar things. Someone already made a <a href="http://www.instructables.com/id/Car-No02-Steering-Wheel-Drive-RC-Car-with-Arduin/" target="_blank">Logitech steering wheel control an R/C car</a>, and a commenter on a blog relating to that project suggested adding force feedback. Another person got an <a href="http://www.youtube.com/watch?v=LHLnw9Yt1rk" target="_blank">XBOX 360 controller to direct</a> an R/C vehicle; another <a href="http://www.instructables.com/id/Use-a-PS3-Controller-to-control-an-Arduino-NXT-Bot/" target="_blank">did the same with a PS3</a> controller. There&#8217;s even a project called &#8216;<a href="http://www.youtube.com/watch?v=dUFvFkPQ73g&#038;feature=player_embedded" target="_blank">Force Fly</a>,&#8217; where the pilot receives force feedback from an aircraft. And my at-present favourite R/C video, &#8220;<a href="http://www.youtube.com/watch?v=cL65rnXiQHg" target="_blank">Tonka Summit Too</a>,&#8221; features a fantastic head-tracking FPV setup. But nothing I saw was exactly what I wanted: a ground based vehicle that could move at high speed, with very little response delay and could therefore be operated in real time <i>over WiFi</i> &#8211; and provide force feedback. And I wanted it to be expandable. And built by me. So I got to work.</p>
<h2 id="awm-parts">Parts List</h2>
<p>The following is a list of components used in the assembly and operation of the Arduino Wifly Mini:</p>
<ul class="postlist">
<li><a href="http://www.tamiyausa.com/product/item.php?product-id=58438" target="_blank">Tamiya Mini M05, and using stock Motor and ESC</a></li>
<li><a href="http://www.greathobbies.com/productinfo/?prod_id=TAM53163" target="_blank">On-Road Tuned Spring Set</a>, Blue on Rear Shocks, Green On Front. (To support the extra weight at the rear of the vehicle.)</li>
<li>One <a href="http://www.dynamiterc.com/" target="_blank">Dynamite </a>7.4v 4500 mAh NiMH Battery, for Tamiya&#8217;s ESC.</li>
<li><a href="http://arduino.cc/en/Main/ArduinoBoardUno" target="_blank">Arduino UNO</a></li>
<li><a href="http://www.sparkfun.com/products/9954" target="_blank">Wifly Shield</a></li>
<li>One plastic housing component salvaged from another R/C vehicle for mounting the UNO and Wifly</li>
<li>One 750mAh 7.4v Battery for Arduino and Wifly shield.</li>
<li>Two Standard-Size metal-gear servos, one for steering and the other for camera panning.</li>
<li>One 6v 1500mAh battery for servo power. (Lots of batteries. The stock ESC&#8217;s power output is too high for the servos, at 7 volts).</li>
<li>One 4.7k Ohm Potentiometer</li>
<li>One <a href="http://www.sparkfun.com/products/9269" target="_blank">Triple Axis Accelerometer, ADXL335</a>, for Force Feedback</li>
<li>Some &#8216;arts and crafts&#8217; wood slats to aid in mounting the pot.</li>
<li>Several ball links and 4-40 rods for steering linkage and potentiometer apparatus, and other components.</li>
<li>Two pieces of plastic cut from the lid of a cheap container: these are used as debris shields to protect the camera&#8217;s lens.</li>
<li>One <a href="http://gopro.com/hd-hero2-cameras/" target="_blank">GoPro HERO 2 Video</a> camera, for FPV. These cameras are excellent.</li>
<li>One <a href="http://www.readymaderc.com/store/index.php?main_page=product_info&#038;cPath=11_30_38&#038;products_id=223" target="_blank">300 mW 1.3gHz video transmitter</a> with appropriate cables and connectors, and stock antenna.</li>
<li>One <a href="http://www.readymaderc.com/store/index.php?main_page=product_info&#038;cPath=53_84&#038;products_id=701" target="_blank">3s 11.1v 1100mAh LiPo for Video TX</a> power.</li>
<li>One <a href="http://www.dpcav.com/xcart/Low-Pass-Antenna-Filter-for-1.2GHz-1.3GHz-TX.html" target="_blank">1.3GHz Low-pass antenna filter</a>, to reduce interference from the video transmitter. (In my case this was not optional.)</li>
<li><a href="http://www.dpcav.com/xcart/Toroid-Ferrite-Core-EMI-RFI-Suppressor-20mm.html" target="_blank">Several ferrite rings</a>, to mitigate interference from electronic equipment. Without the low-pass filter and the ferrite rings, the Wifly can&#8217;t hear the base station when the video transmitter is powered-on. Don&#8217;t underestimate the effectiveness of the ferrite ring! They&#8217;re worth having.</li>
<li>One <a href="http://www.readymaderc.com/store/index.php?main_page=product_info&#038;cPath=11_34_44&#038;products_id=224" target="_blank">1.3gHz video receiver</a></li>
<li>One <a href="http://www.readymaderc.com/store/index.php?main_page=product_info&#038;cPath=11_45_52&#038;products_id=196" target="_blank">8dBi patch antenna for receiver</a>.</li>
<li>A couple of 12volt deep cycle batteries, a Pure-Sine Wave inverter, and a regular monitor for FPV viewing when in the field. Bulky!</li>
<li><a href="http://www.amazon.ca/Cisco-Linksys-WRT54GL-Wireless-G-Broadband-Router/dp/B000BTL0OA/ref=sr_1_1?ie=UTF8&#038;qid=1335071646&#038;sr=8-1" target="_blank">WRT54GL Router</a> for base station. Excellent router when paired with DD-WRT Firmware, as I have.</li>
<li>2.4 GHz Yagi (12dBi) and Patch antenna (14dBi) for base station, both from a local electronics store.</li>
<li>Tripod Stand for Mounting antennas.</li>
<li>Various connecting cables for base station (RP-SMA, TNC, N-Type, Cat-5, Etc).</li>
<li>Macbook, for base station.</li>
<li>Controllers (so far): <a href="http://en.wikipedia.org/wiki/Xbox_360_Controller" target="_blank">Xbox360</a>, <a href="http://en.wikipedia.org/wiki/PS3_Controller#DualShock_3" target="_blank">PS3</a>, <a href="http://www.logitech.com/en-ca/gaming/wheels/devices/5184" target="_blank">G27 Steering Wheel</a>, <a href="http://www.logitech.com/en-ca/gaming/controllers/devices/rumble-gamepad-f510" target="_blank">F510 rumblepad</a>.</li>
</ul>
<h2 id="awm-cav">Choosing a Vehicle</h2>
<p>My first choice of vehicle was a Jeep Rubicon &#8216;replica&#8217; from Toys&#8217;R'Us. I bought it because it was inexpensive and seemed to be a good platform for the equipment I&#8217;d be mounting in it. Wouldn&#8217;t it be neat to turn a cheap R/C toy into something far more?</p>
<div id="image-in-post-block"><img src="http://www.blairkelly.ca/wp-content/uploads/2012/04/JeepRubicon.jpg" alt="" title="Toy Jeep Rubicon" width="640" height="425" class="alignnone size-full wp-image-2725" />
<div id="image-in-post-desc">Toy Jeep Rubicon, Wifly Project&#8217;s First Vehicular Candidate</div>
</div>
<p>This idea turned out to be false economy. Modifications proved much more difficult than I anticipated. For example, I was not content on having a vehicle that could turn only left or right, with nothing in between. What good is a car without finite or gradual steering capability? (<i>Other than</i> guaranteed to flip at high speed&#8230;) Adding a servo-driven steering system to the existing body proved to be the breaking point for this vehicle&#8217;s inclusion in the project. And there were other problems: The motor mount in this vehicle is totally custom and can accept only one size of generic, low-quality motor. I ended up purchasing an ESC (<a href="http://en.wikipedia.org/wiki/Electronic_speed_control" target="_blank">Electronic Speed Control</a>) to govern the forward and reverse directions, since building an <a href="http://en.wikipedia.org/wiki/H_bridge" target="_blank">H-bridge</a> to do the same thing was a bit too granular, too close to the building blocks, for the type of project I wanted this to be. And the wheels are not easily replaceable, and same goes for most other critical components. And I don&#8217;t have much confidence the parts can be ordered separately, or for very long after the vehicle is discontinued, which probably it already is. If you decide on trying this project yourself, I recommend you get a remote control vehicle from a reputable manufacturer like Tamiya or Traxxas. You&#8217;ll save yourself a lot of time and money in the long run. Of course, you could build a custom vehicle if you wished. If so, remember it is a highly skilled business if attempting to make something robust and reliable.</p>
<p>I ended up buying a Tamiya Mini with an MO5 chassis. It wasn&#8217;t much more expensive than the Rubicon. It was a &#8220;build kit,&#8221; which means all the pieces had to be put together one screw at a time. That wasn&#8217;t a problem for me. The process was long but enjoyable and informative. If something goes wrong with the vehicle I have a much better chance of successfully diagnosing the problem, and obtaining the necessary parts to fix it.</p>
<div id="image-in-post-block"><img src="http://www.blairkelly.ca/wp-content/uploads/2012/04/Tamiya-Mini-M05.jpg" alt="" title="Tamiya Mini M05" width="640" height="478" class="alignnone size-full wp-image-2729" />
<div id="image-in-post-desc">Tamiya Mini with M05 Chassis (Front-Wheel Drive). Before Modifications.</div>
</div>
<p>From the get-go, this model was far more promising. It has graded steering thanks to the steering servo, and maintenance is possible if only sometimes a bit of a hassle. And it is much, much faster than the Rubicon.</p>
<h2 id="awm-moc">Means of Communication</h2>
<p>This section deals with the methods of communication employed in the Arduino Wifly Mini project.</p>
<p><b id="awm-gcom">General Communication</b></p>
<p>The electrical components of an R/C vehicle are typically controlled by a small onboard receiver. The receiver gets its instructions from the transmitter, usually held in the driver&#8217;s hands. Most receivers don&#8217;t send information back to the transmitter, <strike>and those that do don&#8217;t have the required capabilities</strike>: Since an Arduino will be controlling the vehicle&#8217;s components and measuring sensor inputs, I needed something that was not only compatible with the Arduino and could receive information wirelessly, but could also send <i>specific</i> information back. That&#8217;s where the <a href="http://www.sparkfun.com/products/9954" title="WIFLY Shield from Sparkfun" target="_blank">Wifly</a> comes in.</p>
<div id="image-in-post-block"><img src="http://www.blairkelly.ca/wp-content/uploads/2012/04/Freshly-Soldered-Wifly-e1333847201424.jpg" alt="" title="Freshly Soldered Wifly" width="558" height="345" class="alignnone size-full wp-image-2762" />
<div id="image-in-post-desc">The Wifly Module, Freshly Soldered. Not bad for my first soldering job in years. <a href="http://www.youtube.com/watch?v=I_NU2ruzyc4" title="How and Why to Solder Correctly" target="_blank">And for having learned from a YouTube video!</a></div>
</div>
<p>The Wifly module allows the Arduino to send and receive information over a WiFi network. It sits on top of an UNO board that runs the vehicle&#8217;s software. The Arduino initiates a serial connection with the Wifly, and the two devices exchange information through this link. The Wifly module talks with the WiFi access point specified in its configuration.</p>
<p>In this project my controllers are connected to a computer running <a href="http://processing.org/" target="_blank">Processing</a>, utilizing a library known as &#8220;<a href="http://creativecomputing.cc/p5libs/procontroll/" target="_blank">ProControll</a>&#8221; to read the controller inputs and initiate force feedback. The Arduino Wifly and Processing exchange information in UDP (User Datagram Protocol) packet form. It is not as reliable as TCP, but it is faster. It is not hard to see why a small response delay is desirable when manoeuvring at high-speed.</p>
<p><b id="awm-vtx">Video Transmission</b></p>
<p>The video feed from the Arduino Wifly Mini is sent via a 1.3GHz 300mW video transmitter. (The battery for this device can be seen on top of the vehicle.) A low-pass filter and ferrite rings were required to reduce interference with the Wifly module. The on-board camera is a GoPro Hero 2. It is an excellent camera and it has many more uses than just FPVing.</p>
<p>When driving at high speed, you need a low-latency video transmission method. The video transmitter I&#8217;m using provides this. Typically, the video feed from a webcam is not fast enough to make safe driving possible. However, the benefits of a WiFi-connected camera that <i>is</i> fast enough are plain to see; wherever you had a WiFi connection, you could see through the &#8216;eye&#8217; of your vehicle. I have my sights set on a product that at the time of writing is yet to be released by GoPro: the <a href="http://gopro.com/hd-hero-accessories/wi-fi-bacpac-remote-combo/" title="WiFi BacPac" target="_blank">WiFi BacPac</a>. It was supposed to come out in March of this year but the release date has been repeatedly pushed back. They now say it will be released in &#8216;Summer 2012&#8242;. Well, when it does come out, I plan to acquire one, and if I do I will give it a test and let you know if it works well enough to use safely with this project. Even if it doesn&#8217;t send images in real-time (or close to it), I&#8217;m sure the WiFi BacPac could be used to provide a video feed for projects that didn&#8217;t involve vehicles moving at such high speed.</p>
<h2 id="awm-ffs">Force Feedback Sensors</h2>
<p>If sending &#8216;force feedback&#8217; from the vehicle, one must decide exactly what force will be sent. I decided to try a couple of possibilities. <i>For reasons described below, and for memory issues, the current version of the software running on the Arduino Wifly Mini does not observe potentiometer values. Also, the G27 steering wheel and F510 rumblepad are currently the only controllers that rumble when instructed by Processing.</i></p>
<p><b id="awm-pot">The Potentiometer</b></p>
<p>I mounted a potentiometer to the body of the vehicle and attached it to the steering linkage. At runtime, the value of the potentiometer at each stage of the steering servo&#8217;s movement, from hard left to hard right, is recorded. Knowing these values gives us a reference to compare values read later, when driving. A discrepancy in expected and actual values would indicate a force on the wheels in a certain direction. A small discrepancy would be a weak force; a large discrepancy would represent a strong force.</p>
<div id="image-in-post-block"><img src="http://www.blairkelly.ca/wp-content/uploads/2012/04/PotView.jpg" alt="" title="Steering Potentiometer" width="640" height="425" class="alignnone size-full wp-image-2789" />
<div id="image-in-post-desc">View of the steering potentiometer installed in the vehicle. Also in this picture: To the right, the Arduino and Wifly mounted between body posts. On either side of the steering linkage and potentiometer, above the motor and wheels, are &#8216;debris shields&#8217; I devised to deflect flying matter away from the camera lens.</div>
</div>
<p>This approach, it turns out, is not reliable. In a controlled setting, with the vehicle sitting on my desk, I had good results when pushing on the wheels. But when driving, the feedback is inconsistent such that it serves to confuse rather than augment the driving experience. I suspect several issues are responsible for this. The Arduino compares stored pot values to present ones. How does it know which recorded value it needs to compare to the present one? It uses the current servo setting. The problem with this is the servo setting isn&#8217;t necessarily where the servo <i>is</i>. This is because it takes time to move the servo horn from one position to another. And under the varying conditions of driving, the time it takes isn&#8217;t always the same. My code estimates the movement delay, but as you can imagine, it is very difficult to produce an acceptably small margin of error. Also, the Mini has a fairly good suspension system, which dampens forces on the steering linkage. Anyway, I&#8217;m not driving the vehicle over many large bumps and nothing in the terrain should cause too much of a force in one direction or another. If I were using a rock crawler with this potentiometer system, and the steering servo was equipped to report its current position, <i>and</i> I was able to control the direction of force feedback in the steering wheel controller, this approach would probably work well when a tire got jammed on terrain. Otherwise, the potentiometer-as-force-feedback-sensor isn&#8217;t worthwhile. At least not in this configuration.</p>
<p><b id="awm-acc">The Accelerometer</b></p>
<p>I mounted a triple-axis accelerometer at the front of the vehicle. I programmed the Arduino to observe this sensor and take the average reading of both the Y (forward &#038; backward) and Z (up &#038; down) axis, over a short period. Deviations in these averages represent a change in acceleration. Detected changes are sent back to Processing, where they&#8217;re converted into a force feedback representation in the controller.</p>
<div id="image-in-post-block"><img src="http://www.blairkelly.ca/wp-content/uploads/2012/04/Triple-Axis-Accelerometer-e1333930791611.jpg" alt="" title="Triple Axis Accelerometer" width="635" height="387" class="alignnone size-full wp-image-2793" />
<div id="image-in-post-desc">Triple Axis Accelerometer</div>
</div>
<p>This approach offers much more reliable and predictable &#8211; and generally more usable &#8211; information. Testing on my desk and while driving produces results you&#8217;d expect: driving over bumpy terrain makes the controller shake, and so will hard acceleration and braking. The amplitude of these forces is mirrored in the strength of the controller&#8217;s rumble. As stated above, memory issues with the Arduino UNO keep me from implementing both the potentiometer and accelerometer sensors, and the latter is superior anyway, so I just run the accelerometer code.</p>
<div id="image-in-post-block"><img src="http://www.blairkelly.ca/wp-content/uploads/2012/04/Accelerometer-Mounted.jpg" alt="" title="Accelerometer, Mounted" width="640" height="478" class="alignnone size-full wp-image-2800" />
<div id="image-in-post-desc">The accelerometer is located in the front bumper of the vehicle. The sensor&#8217;s wires, leading to the Arduino, are visible between the shocks.</div>
</div>
<h2 id="awm-mfwm">More Features Worth Mentioning</h2>
<p>The beauty of Arduino is its&#8230; everything. It is a diverse platform. You can do so much with it. I&#8217;m only starting, and I want to squeeze as much out of it as I possibly can.</p>
<p><b id="awm-escswitch">The ESC Switch</b></p>
<p>Using an opto-isolator chip connected to the Arduino, I can control remotely the power-state of the ESC. I did this primarily for safety reasons. If my code detects an error, such as multiple instructions received without a proper terminating character between each (a feature of an older version of the code, no longer posted), or the absence of a heartbeat character for a set period of time, the car performs a &#8220;take-no-chances&#8221; three-second full reverse (only if in forward throttle) and then cuts power to the ESC. This is to prevent a runaway, which of course could lead to an &#8216;obliterated vehicle&#8217; situation. Or in the worst-case, somebody could get hurt. Both scenarios are highly undesirable.</p>
<div id="image-in-post-block"><img src="http://www.blairkelly.ca/wp-content/uploads/2012/04/OptoIsolator.jpg" alt="" title="Opto-Isolator Chip with Current Limiting Resistor" width="640" height="478" class="alignnone size-full wp-image-2803" />
<div id="image-in-post-desc">Opto-Isolator Chip with Current Limiting Resistor, for remote ESC power-state control. It does what its name suggests. The voltage of the ESC is separated from the voltage of the Arduino&#8217;s PWM output by means of an optically sensitive switch and an LED, all inside this little unit.</div>
</div>
<p><b id="awm-cps">The Camera-Panning Servo</b></p>
<p>The limitations of a static camera will become apparent very soon after you start driving an FPV vehicle. Enthusiasts have created an array of solutions for this, and I&#8217;ve noticed <a href="http://www.youtube.com/watch?v=ZxxVZKpC3aw" target="_blank">they&#8217;re often custom</a>. This is because everybody&#8217;s situation usually differs somewhat in terms of hardware or goals or both. The Mini is so small that a tilting camera was not really an option, at least not with the equipment I have, and it wasn&#8217;t within the limited scope of this phase of the project. But a panning camera seemed like a must. I made some modifications to the body to accomodate a servo. It had to go on the roof. I suppose I could have finagled some kind of mount that attached to the chassis, but there is a lot more equipment to be added when implementing FPV than just a servo and a camera (transmitter, battery, connecting cables, ferrite rings, low-pass filter), and I wanted some degree of modularity. The only thing I lamented was the look of the car, but I got over that pretty quickly. I will focus on looks for the project&#8217;s next phases.</p>
<div id="image-in-post-block"><img src="http://www.blairkelly.ca/wp-content/uploads/2012/04/Panning-Servo.jpg" alt="" title="Panning Servo" width="640" height="478" class="alignnone size-full wp-image-2804" />
<div id="image-in-post-desc">Panning servo mounted in the roof of the Wifly Mini. You can also see I&#8217;ve cut out the front windshield for better visibility.</div>
</div>
<h2 id="awm-nwc">A Note on Wifly Configuration</h2>
<p>If you&#8217;re like me, you&#8217;ll sometimes be frustrated when confronted with obstacles that are to you totally opaque but, to experienced individuals, so transparent it isn&#8217;t worth mentioning. I ran into this when attempting to configure the Wifly module.</p>
<p>The Wifly is a wonderful device that is highly configurable. I recommend you download the manual and familiarize yourself with the various commands. Don&#8217;t skip this step, as time consuming as it might seem. You&#8217;ll save yourself some hassle and probably discover information you&#8217;ll find useful later on. Once you&#8217;ve done this and you have the Wifly module attached to the Arduino (which should be connected to your computer), make sure you have the Wifly library loaded and accessible to the Arduino development tool. If done properly, you should see an example sketch titled SpiUartTerminal. Load that onto the Arduino. In the serial monitor, if you reboot the Arduino board you&#8217;ll see a line that reads something like, &#8216;press $$$ to enter commands.&#8217; In my initial experimentation, I could enter command mode but I could not issue commands. This is because, it turns out, the version of Arduino I&#8217;m using doesn&#8217;t send a carriage return even when you press the return key! The Wifly recognizes, in its default state at least, the carriage return as the end of a command and a request from the user to execute it. So in order to issue commands I needed to connect to the Arduino through the &#8216;Terminal&#8217; program on my mac. To do this, your Arduino should be plugged in via USB to your Mac just like it would be when loading a program, except you&#8217;ll be using Terminal (or some equivalent in Windows) to open a serial connection instead of the Arduino IDE. You&#8217;ll have to enter the following into the Terminal prompt (your location, i.e. &#8216;usbmodem621&#8242; as seen below, will probably differ slightly. It could be usbmodem2201 or usbmodem323, or something like that):</p>
<div id="codecontainer">

<div class="wp_syntax"><table><tr><td class="line_numbers"><pre>1
</pre></td><td class="code"><pre class="language" style="font-family:monospace;">screen /dev/tty.usbmodem621 9600</pre></td></tr></table></div>

</div>
<p>When you hit enter, the Terminal should connect to the Arduino (make sure you have that SpiUartTerminal sketch loaded!) and you&#8217;ll be able to enter &#8216;$$$&#8217; to start issuing commands. This project requires the Wifly be set to UDP or combo mode.</p>
<h2 id="awm-code">The Code</h2>
<p>The code for this project is a wild beast. This is practically my first (but literally my second) Arduino project and before this I had never programmed in C. <i>Gradually, I will add highlights from the code below. <b>There are still many improvements to be made to this code.</b></i></p>
<p>I want to learn how to make C classes, and soon! ;)</p>
<p>Soon after I finished the first phase of the Arduino Wifly Mini project, I started learning about PPM signals. I have now realized that my method of sending data back and forth from the Arduino is flawed: instead of sending packets with each instruction, I should be putting a series of instructions (steering, throttle, camera, etc) into one packet, so the method more greatly resembles a PPM stream. I will be making this modification to the code and will update it here when finished. <i>This update has now been made to the code.</i></p>
<h3 id="awm-codepermissions">Permissions</h3>
<p>I&#8217;ve been asked if the code can be used, modified, and republished. The answer is yes! Feel free to use the code however you like. My only request is this: if you use the code verbatim or you found it helped you substantially, please just make a note of it somewhere in your published project. Thanks! And have fun. :)</p>
<h3 id="awm-codelibs">Libraries</h3>
<p>Between Processing and Arduino, there are a few (that&#8217;s not to say many) libraries in use. Check the import statements in the full code to see them all. Of note are the following. On the Processing side is Hypermedia NET (for UDP communication) and <a href="http://creativecomputing.cc/p5libs/procontroll/" title="ProControll" target="_blank">ProControll</a>, a great little library that&#8217;ll read the inputs of a wide range of devices. Though I wish it would handle controller rumbling better: that way we could have the XBOX360 controller shaking. On the Arduino side, we need the standard <a href="http://forum.sparkfun.com/viewtopic.php?f=32&#038;t=25216" title="Wifly Library" target="_blank">WiFly library</a> in order to use the SpiSerial object.</p>
<h3 id="awm-acode">Arduino Code</h3>
<p><i>I have placed the whole Arduino code within it&#8217;s own post to keep this page a reasonable length and make updates easier. <a href="/?p=2823">Click here to see the Arduino sketch.</a></i></p>
<p><b id="awm-spftaw">Sending Packets from the Arduino Wifly</b></p>
<p>One of the larger initial puzzles I faced was how to send packets from the Arduino/Wifly to the computer running Processing. I was able to do it the other way around fairly easily. I could find nothing that explained how to send packets from the Wifly. I <a href="http://forum.sparkfun.com/viewtopic.php?f=13&#038;t=25428&#038;sid=49da19e8e5248a0e1e6f38776ecdaaa2&#038;start=15" title="UDP Wifly Packet Discussion" target="_blank">found a thread on SparkFun</a> that explained how to put the Wifly into UDP mode, and a few people commented they were able to send UDP packets from the Wifly, but didn&#8217;t say exactly <i>how</i>. I decided to scour the Wifly&#8217;s manual. And whatd&#8217;ya know? The answers were there. </p>
<p>Like the SparkFun forum thread indicated, the Wifly has to be put into UDP packet mode using the SPI-UART Terminal Sketch, or similar method. (This only has to happen once.) I figured out that you then have to write information to the SPI register/buffer on the Wifly. When you&#8217;re ready to send the packet, the buffer needs to be &#8216;flushed.&#8217; Here is a simplified version of the code I use to do this:</p>
<div id="codecontainer">

<div class="wp_syntax"><table><tr><td class="line_numbers"><pre>1
2
3
4
5
6
7
8
9
10
11
12
</pre></td><td class="code"><pre class="language" style="font-family:monospace;">void ptb(char c)
{
  //prints to SpiSerial (fills up the buffer).
  SpiSerial.print(c, BYTE);
}
void fb()
{
  //FLUSH BUFFER (sends buffer contents to host).
  //a carriage return character (hex 0xd) flushes the WIFLYs buffer and sends the msg
  //where FBC is defined as: char FBC = 0xd;  //carriage return.
  SpiSerial.print(FBC, BYTE);
}</pre></td></tr></table></div>

</div>
<p>The Wifly then forms a proper UDP packet with the payload set as whatever you printed to the SPISerial before issuing the Flush command. Voila! Remember, you have to begin the serial connection in order to use the SpiSerial object, just like you&#8217;d have to begin a regular Serial connection between the Arduino and your computer. It looks like this, and is placed in your setup function:</p>
<div id="codecontainer">

<div class="wp_syntax"><table><tr><td class="line_numbers"><pre>1
</pre></td><td class="code"><pre class="language" style="font-family:monospace;">SpiSerial.begin();</pre></td></tr></table></div>

</div>
<p>If you want, you can set the Wifly to &#8216;flush&#8217; the buffer once it fills with a certain amount of data. Or you can disable that feature and flush the buffer when the Wifly sees a specified character. (The flush command should work even if you have a buffer limit set). I believe the default character is a carriage return.</p>
<h3 id="awm-pcode">Processing Code</h3>
<p><i>Same as Arduino code. <a href="/?p=2842">Click here to see the whole Processing sketch.</a></i></p>
<p><b id="awm-udppl">UDP Packet Listening</b></p>
<p>To listen for packets in Processing on a specific port, you need the following code in your setup function:</p>
<div id="codecontainer">

<div class="wp_syntax"><table><tr><td class="line_numbers"><pre>1
2
</pre></td><td class="code"><pre class="language" style="font-family:monospace;">udp = new UDP( this, listenPort );  //port 6000
udp.listen( true );</pre></td></tr></table></div>

</div>
<p>Then, somewhere in your code you&#8217;ll need a read function. This is what mine looks like:</p>
<div id="codecontainer">

<div class="wp_syntax"><table><tr><td class="line_numbers"><pre>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
</pre></td><td class="code"><pre class="language" style="font-family:monospace;">void receive( byte[] data ) {
  //void receive( byte[] data, String ip, int port ) {   // &lt;-- extended handler
  char[] mrTdata = new char[data.length]; //FOR CONVERTING BYTE TO CHAR. here is stored information coming from the arduino.
  String incomingDataString = &quot;&quot;; //makes a whole string out of data presently being received...
  String firstChar = &quot;&quot;;
  String currentChar = &quot;&quot;;
&nbsp;
&nbsp;
  for (int i=0; i &lt; data.length; i++) {
    mrTdata[i] = char(data[i]);   //BYTE TO CHAR.
    incomingDataString = incomingDataString + mrTdata[i];  //added to whole string used for reference.
    firstChar = &quot;&quot; + mrTdata[0];   //store the first char for reference... &quot;&quot; appears to be necessary to force conversion from CHAR to STRING
    currentChar = &quot;&quot; + mrTdata[i];
&nbsp;
    if(!currentChar.equals(&quot;1&quot;) &amp;&amp; !currentChar.equals(&quot;2&quot;) &amp;&amp; !currentChar.equals(&quot;3&quot;) &amp;&amp; !currentChar.equals(&quot;4&quot;) &amp;&amp; !currentChar.equals(&quot;5&quot;) &amp;&amp; !currentChar.equals(&quot;6&quot;) &amp;&amp; !currentChar.equals(&quot;7&quot;) &amp;&amp; !currentChar.equals(&quot;8&quot;) &amp;&amp; !currentChar.equals(&quot;9&quot;) &amp;&amp; !currentChar.equals(&quot;0&quot;) &amp;&amp; !currentChar.equals(&quot;.&quot;)) { 
      //the character is not a number, not a value to go along with a command,
      //so it is probably a command.
      if(instructionDataString != &quot;&quot;) {
        aCommandVal = Integer.parseInt(instructionDataString);
      }
      if(aCommand != &quot;&quot;) {
        delegate();
      }
      aCommand = currentChar;
      instructionDataString = &quot;&quot;;
    } else {
      //in this case, we're probably receiving a command value.
      //store it
      instructionDataString = instructionDataString + currentChar;
    }
&nbsp;
  }
}</pre></td></tr></table></div>

</div>
<p>When data is received, the above function looks at it one character at a time. The function determines if the character is a command or data. Anything that&#8217;s a number or a decimal is stored in string format as a &#8216;command value.&#8217; Anything else is stored as a command. When another character that isn&#8217;t a number or decimal is received the function throws the stored command and (if applicable) accompanying value at the delegate function.</p>
<p><b id="awm-cme">Controller-Map Example</b></p>
<p>Here is an example of a controller map, for the PS3 controller in this instance. My processing code utilizes the information set within this function in conjunction with the <a href="http://creativecomputing.cc/p5libs/procontroll/" target="_blank">ProControll library</a>. The code then makes use of the various inputs from the controller through a large series of functions. To understand the basics of using a controller with ProControll I recommend reading through the examples offered by the library&#8217;s authors. If you&#8217;d like to see how my code makes use of the various controllers, I recommend <a href="http://www.blairkelly.ca/2012/04/20/arduino-wifly-mini-processing-code/">viewing the entire Processing sketch</a> and following the trail from the main draw() function. You&#8217;ll see the readControlDevice() function calls a set of more functions (sliderHandling(), buttonHandling(), etc) that watch &#8211; you guessed it &#8211; each slider and button on the controller, and those functions call more functions (shClutch(), shCamera(), shThrottle, etc) depending on what the controller readings are.</p>
<div id="codecontainer">

<div class="wp_syntax"><table><tr><td class="line_numbers"><pre>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
</pre></td><td class="code"><pre class="language" style="font-family:monospace;">void mapPS3mac()
{
  controlDevice = controll.getDevice(&quot;PLAYSTATION(R)3 Controller&quot;);
  printDeviceItems();   //prints a list of available controls.
  controlDevice.setTolerance(0.05f);  //was 0.5f.
  wheelMovementMin = 0.001;  //wheel has to be moved this much in order to calculate new value.
  wheelStickDeadZone = 0.01;    //stick neutral deadzone. If stick reading is within + or - this value, it's as good as in the centre.
  wheelHardValue = 0.93;  //if the stick is moved to the right or left this amplitude then we consider it moved left or right 100%
  wheelPOW = 1.0;  //I haven't developed this fully, tinker with caution. It is intended to add progressive steering.
  throttleStickDeadzone = 0.05; //throttle neutral deadzone.
  pedalDeadZone = 0.004;  //if the pedal reading is under this value then we consider it not-activated.
  pedalMovementMin = 0.004;  //minimim distance pedal must be moved to calculate a new value
  pedalHardValue = 0.95;    //if pedal is depressed more than this amount then we consider it fully activated...
  cameraMoveInterval = 6; //milliseconds between camera steps.
  cameraMovementMin = 0.01; //if camera movement is controlled by a stick, the stick has to be moved this much to calculate a new value.
  cameraStickDeadZone = 0.07;
  cameraHardValue = 0.90;
  cameraMaxStep = 7;
  cameraMinStep = 4; 
  hasCoolieHat = true;    //the controller has a cooliehat.
  coolieIsCoolie = false;  //the cooliehat is broken into buttons.
  clutchForGears = true;  //does the clutch need to be depressed to change gears?
  hasSlidingClutch = false;
  hasForRevGear = false;  //false. used to determine if we should pay attention to &quot;direction&quot;.
  oneSliderForThrottle = true; //one stick, on the left, signifies both directions...
  hasReversePedal = false; //this controller does not have a dedicated reverse pedal.
  wheelIsStick = true;    //steering is controlled by a stick. (as opposed to a wheel, like the G27)
  throttleIsStick = true; //make sure you use stick deadzones.
  hasCameraStick = false;   //is camera control a stick on this controller?
  hasCameraButtons = true;
  hasCameraCentreButton = true;
  hasRumblers = 2;  //controller has rumblers, though I can't get them to work yet for the PS3 or Xbox360 controller.
  maxRumbleIntensity = 1.0;  //max rumble intensity...
  throttleBase = 2;  //0 = -1 to 1, rests at 0. 1 = -1 to 1, rests at -1. 2= SPLIT across two padels, rests at 0, range: 1 to -1.
  reverseBase = 0;
  clutchBase = 0;
  //sliders
  sliderWheel = controlDevice.getSlider(2);
  sliderThrottle = controlDevice.getSlider(1);
  //sliderCameraX = controlDevice.getSlider(1);
  //buttons
  buttonStart = controlDevice.getButton(3);      //START BUTTON
  buttonESC = controlDevice.getButton(0);     //SELECT BUTTON
  buttonSendSettings = controlDevice.getButton(3);      //START BUTTON, clutch must be in.
  buttonResetOnFlatline = controlDevice.getButton(0);      //SELECT BUTTON (clutch must be in for this one to work).
&nbsp;
  buttonGEARDOWN = controlDevice.getButton(10);       //LTB
  buttonGEARUP = controlDevice.getButton(11);        //RTB
  buttonCLUTCH = controlDevice.getButton(2);    //RJD
&nbsp;
  buttonCentreCamera = controlDevice.getButton(1);    //LJD (depress to adjust camera trim).
  buttonAutoCamera = controlDevice.getButton(14);    //X. autoCamera ON/OFF
  buttonCameraMoveLeft = controlDevice.getButton(8);       //LEFT PADDLE
  buttonCameraMoveRight = controlDevice.getButton(9);        //RIGHT PADDLE
&nbsp;
  buttonCalibrateSensors = controlDevice.getButton(12);        //TRIANGLE  (clutch must be in. calibrates sensors. Vehicle should be stopped!!)
  buttonSendFFaccels = controlDevice.getButton(12);        //TRIANGLE (when clutch is NOT in).
&nbsp;
  //cooliehat
  //broken into buttons for this controller.
  //(left arrow pad)
  coolieUP = controlDevice.getButton(4);    //cooliehat UP
  coolieDOWN = controlDevice.getButton(6);    //cooliehat DOWN
  coolieLEFT = controlDevice.getButton(7);    //cooliehat LEFT
  coolieRIGHT = controlDevice.getButton(5);    //cooliehat RIGHT 
}</pre></td></tr></table></div>

</div>
<h2 id="awm-nai">Notes and Ideas</h2>
<p><b id="awm-overall">Overall Performance</b></p>
<p>The performance of the Wifly Mini has exceeded my expectations. Steering, throttle, camera servo and force feedback are all fast and responsive under ideal conditions. (In my house.)</p>
<p><b id="awm-bulk">Bulk</b></p>
<p>The system is extremely bulky. (I&#8217;m talking about all the components needed to run the vehicle). I put this down mostly to the project being in its very early stages. At present I&#8217;m spending nearly an hour to set up in the field, which in the long run will be a total buzz-kill. <strike>It would be nice to be able to eliminate the computer and have an Arduino with, say, a USB host shield handle the the controller of choice. I believe this will be possible but isn&#8217;t planned for the near future.</strike> I have found an incredible project that would help me cut out the laptop. It&#8217;s called <a href="http://www.youtube.com/watch?v=WunF92MfV8Q" title="USB2PPM TX Project" target="_blank">USB2PPM</a>. If I can find more information about it, I will implement it into a new version of the Arduino Mini. This would be a major change to the project, since it would cut out the Wifly, but might be necessary if I want long-range communication (which I do). I will probably end up building my own version of USB2PPM and sharing the details on my site.</p>
<p><b id="awm-range">Range</b></p>
<div id="image-in-post-block"><img src="http://www.blairkelly.ca/wp-content/uploads/2012/04/BlairRangeTest.jpg" alt="" title="Blair Range-Tests the Wifly Mini" width="640" height="425" class="alignnone size-full wp-image-2957" />
<div id="image-in-post-desc">I range test the Mini. Can you spot me?</div>
</div>
<p>I&#8217;ve been able to take the car out a few times to a track and a large parking lot for range testing. When the model is close (under 75 metres away), it performs very well. But anything beyond that and things start to get very glitchy. This is despite having a signal amplifier on my router and directional antennas. One of the major components of the next phase of this project is to improve range: I intend to build an antenna tracker and fiddle with the <a href="http://www.dd-wrt.com/site/index" target="_blank">DD-WRT</a> firmware on my <a href="http://en.wikipedia.org/wiki/Linksys_WRT54G_series" target="_blank">WRT54GL</a> router. I&#8217;ll try turning off ACK timing and playing with other relevant settings.</p>
<div id="image-in-post-block"><img src="http://www.blairkelly.ca/wp-content/uploads/2012/04/blairRangeTestCloser.jpg" alt="" title="A Closer Look" width="1091" height="775" class="alignnone size-full wp-image-2960" />
<div id="image-in-post-desc">A Closer Look: There I am!</div>
</div>
<p>When the car was on the ground, I could only achieve about 100 metres distance from the base station. But when picked up, the range improved dramatically. I suspect this has something to do with the ground somehow attenuating the signal. I&#8217;m not sure at the time of writing. I am going to try to get the Arduino Wifly Mini&#8217;s antenna further off the ground.</p>
<div id="image-in-post-block"><img src="http://www.blairkelly.ca/wp-content/uploads/2012/04/signalStrengthFromSeveralHundredMetres.jpg" alt="" title="Signal Strength from Several Hundred Meters Distance" width="1280" height="954" class="alignnone size-full wp-image-2964" />
<div id="image-in-post-desc">Signal Strength at Several-Hundred Meters Distance</div>
</div>
<p><b id="awm-changefrequencies">Changing Frequencies</b></p>
<p>With all the research I&#8217;ve done on 2.4GHz (the frequency in which most WiFi operates), I&#8217;ve learned that it is highly vulnerable to <a href="http://en.wikipedia.org/wiki/Attenuation" target="_blank">signal attenuation</a>, particularly as a result of uneven and obstructive terrain. So after a bit of research I suspect 433MHz might be the way to go for long range control. Specifically I have my eyes on <a href="http://www.immersionrc.com/EzUHF.htm" target="_blank">EzUHF </a>and <a href="http://blog.flytron.com/category/openlrs-project" target="_blank">OpenLRS</a>: both seem promising. The longer wavelength will mean less bandwidth, but telemetry and force feedback (at least in the form I&#8217;ve concocted) aren&#8217;t very bandwidth-heavy. So this would constitute an Arduino Ham Mini. <a href="http://www.imdb.com/title/tt0112431/" target="_blank">Babe</a> could be the mascot!</p>
<div id="image-in-post-block"><img src="http://www.blairkelly.ca/wp-content/uploads/2012/04/JessicaParkingLot.jpg" alt="" title="2.4ghz Directional Antennas" width="640" height="425" class="alignnone size-full wp-image-2955" />
<div id="image-in-post-desc">Jessica stands over the Wifly Mini during range testing. 2.4 GHz directional antennas are aimed at the car. In this shot, the connection had been already lost. The Wifly has a hard time hearing signals from the base station when its antennas are close to the ground.</div>
</div>
<p><b id="awm-platformsize">Platform Size</b></p>
<p>In the next phases of the project I will be moving to something larger. The Mini M05 is simply too small for what I want to do. (The cement lip of a driveway is an impassable obstacle.) It is fun to drive around the house on a single level, but the scenery quickly becomes repetetive. And outside, the Mini is confined to clean parking lots or tracks, both of which often have many people and cars to accidentally drive a prototype into&#8230; or under. Besides, I want to add a lot more sensors to the vehicle, such as a GPS module. I&#8217;d also like to add in a true brake to make the pedal on the G27 wheel behave more as one would expect. And with more than one transmission frequency in the mix, the proximity of radio devices onboard makes it hard to keep interference down. Suddenly I&#8217;ve had an idea&#8230;</p>
<div id="image-in-post-block"><img src="http://www.blairkelly.ca/wp-content/uploads/2012/04/monsterMini.jpg" alt="" title="Monster Mini!" width="640" height="425" class="alignnone size-full wp-image-2976" />
<div id="image-in-post-desc">Monster Mini! Raaaaaa!!</div>
</div>
<p><b id="awm-cellcom">Communication Through Cellular Broadband</b></p>
<p>I was able to associate the Wifly with my iPhone hotspot, and with some adjustments to my home firewall I was able to exchange some packets, but none of this worked in the way we might wish. I was not able to send packets to my cell phone and then to the Wifly module. Rather, when my computer &#8211; the one running processing &#8211; was associated with the cell phone&#8217;s hotspot, I was able to send packets to my home network and control the Wifly there. This state of affairs is the wrong way around, as you doubtless see if you consider I&#8217;m after long range control from a base station. Another thing: there was far too much latency to make safe driving a reality even if the cell phone <i>could</i> receive and route packets from external sources. However, my cell phone has only 3G connectivity, and it <i>is</i> a member of the walled-garden iPhone variety after all. So I have high hopes for LTE and Android phones, where one might be able to devise a custom and open source program to exchange UDP packets at high speed. Imagine an R/C car operated remotely through an LTE network in real time with a video uplink. Theoretically you could drive across Canada, given adequate cell coverage and <i>a lot</i> of helpful hobbyists&#8230;</p>
<h2 id="awm-uh">Update History</h2>
<p><b>April 30, 2012:</b> <i>Added Controller-Map Example to Processing Code section.</i><br />
<b>April 29, 2012:</b> <i>Added &#8216;Video Transmission&#8217; section to writeup under Means of Communication. Just to be clear on how the VTX is presently being done.</i><br />
<b>April 28, 2012:</b> <i>Updated Processing and Arduino code. Removed complicated braking functions. Simplified packet communications (both directions); payloads now more resemble PPM-style configuration, instead of the one-command-per-packet method I was using. Also simplified accelerometer/force-feedback functions.</i><br />
<b>April 22, 2012:</b> <i>Added &#8216;Permissions&#8217; to code section.</i><br />
<b>April 21, 2012:</b> <i>Added parts list.</i></p>
]]></content:encoded>
			<wfw:commentRss>http://www.blairkelly.ca/2012/04/20/arduino-wifly-mini/feed/</wfw:commentRss>
		<slash:comments>41</slash:comments>
		</item>
		<item>
		<title>Arduino Wifly Mini: Arduino Code</title>
		<link>http://www.blairkelly.ca/2012/04/20/arduino-wifly-mini-arduino-code/</link>
		<comments>http://www.blairkelly.ca/2012/04/20/arduino-wifly-mini-arduino-code/#comments</comments>
		<pubDate>Fri, 20 Apr 2012 19:25:56 +0000</pubDate>
		<dc:creator>blair</dc:creator>
				<category><![CDATA[Arduino]]></category>
		<category><![CDATA[Code]]></category>
		<category><![CDATA[CodeSingle]]></category>
		<category><![CDATA[Multimedia]]></category>
		<category><![CDATA[arduino]]></category>
		<category><![CDATA[code]]></category>
		<category><![CDATA[Mini]]></category>
		<category><![CDATA[Wifly]]></category>

		<guid isPermaLink="false">http://www.blairkelly.ca/?p=2823</guid>
		<description><![CDATA[Code for the Arduino Wifly Mini, Arduino side.]]></description>
			<content:encoded><![CDATA[<p>Here is the Arduino script for the <a href="/2012/04/09/arduino-wifly-mini/">Arduino Wifly Mini</a> project. Please note this code was written in the Arduino version 0023 environment.</p>
<div id="codecontainer">

<div class="wp_syntax"><table><tr><td class="line_numbers"><pre>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
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
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
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
</pre></td><td class="code"><pre class="language" style="font-family:monospace;">//Arduino WIFLY Mini
//now with FPV
//Two-Way Communications
//Direct SPI-UART manipulation.
//
//ADDED ACCELEROMETER FEEDBACK, REMOVED pot reading/logging, DUE TO MEMORY ISSUES.
//removed complicated braking functions.
//simplified communications method, now transmissions to (and from) the wifly are bundled, to more resemble a PPM stream
float AWMV = 1.110;   //arduino wifly mini version (AWMV).
&nbsp;
#include &quot;WiFly.h&quot; // We use this for the preinstantiated SpiSerial object.
#include &lt;Servo.h&gt;
&nbsp;
//WIFLY SETTINGS:
boolean performSetup = false; //should we set these settings? or skip them?
boolean requireFirstContact = false; // do we need to make first contact with AP before entering main loop?
//String hostIP = &quot;192.168.1.4&quot;;  //mbp, wireless, Hitch
String hostIP = &quot;192.168.1.6&quot;;  //Blair WIRED, Hitch
String ssid = &quot;Hitch&quot;;            //WRT54GL
String phrase = &quot;TwentyTwo&quot;;
String listenPort = &quot;8888&quot;;
String sendPort = &quot;6000&quot;;
&nbsp;
//ARDUINO SETTINGS AND VARIABLES:
//PIN DEFINITIONS
const int potPin = A0;    //potentiometer input pin.
const int pinAccel1X = A1;    //accelerometer #1 Xplane output.
const int pinAccel1Y = A2;    //accelerometer #1 Yplane output.
const int pinAccel1Z = A3;    //accelerometer #1 Zplane output.
const int pinInfoLED = 8; //pin on which information LED is found.
const int pinOpto = 4; //opto-isolator pin, for turning on ESC.
const int pinThrottle = 5; //throttle/drive output
const int pinSteer = 6; //the pin that writes location to steering servo.
//SERVOS
Servo theCamera;  //this variable references the steering servo.
Servo theWheel;  //this variable references the steering servo.
Servo theGas; //the variable for referencing the ESC.
//SERVO-AT-REST SETTINGS
int cameraCentre = 90; //default centre position of camera servo.
int wheelCentre = 92; //default wheel center. Official value comes from processing.
//SERVO RANGE SETTINGS (Movement, Allowances)
//steering
int steeringRange = 8; //default steering range.
int middleRange = 4;   //how far on either side of wheelCentre will the arduino correct for a constant feedback from the steering servo pot?
int autoCameraRange = 50; //default movement degrees either side of center for camera servo.
//throttle
int throttleNeutral = 50; //no throttle. SAFETY VALUE.
int throttleFullReverse = 30;  //be careful with this.
int throttleSetting = throttleNeutral; //this is for Arduino's reference when error handling, etc.
//camera
//int cMMdelay = 3300; //cameraMoveManualDelay, delay in milliseconds before arduino reverts back to auto camera movement, if set.
//unsigned long cMMtimeout = millis();
//
//SERVO Bearing/Setting Memory and Variables
//steering
//int cBearing; //this is the currently-set steering servo bearing.
//int sBearing; //this is the setting of the servo guessed at by the arduino; a rough estimate.
//unsigned long sStime;   //steering servo time; if millis is beyond this, then tick the servo one degree toward cBearing, then reset.
//unsigned long oldBearing;   //when this is up, then the bearing is considered old. move back to strict pot threshold.
//FORCE FEEDBACK: Settings, Values, Ranges, Allowances, Variables
unsigned long accelCheckTime;
int accelCheckDelay = 33; //milliseconds between accel checks.
int accel1counter = 0;  //keeps track of where we are in the accel1 average arrays, used for determining what the average of the last 100 readings have been.
const int accelAVGarraySize = 12;
int accel1avgXarray[accelAVGarraySize];
int accel1avgYarray[accelAVGarraySize];
int accel1avgZarray[accelAVGarraySize];
boolean FFaccelsCalibrated = false; //are the accelerometers calibrated?
boolean FFsendACCELDATA = false;   //should the arduino check the ACCELEROMETER(s) and send back information to the driver?
int minAccelFFamplitudeX = 27; //default sent by host at calibrate sensors (or send thresholds).
int minAccelFFamplitudeY = 5; //default... sent again by host at calibrate sensors.(or send thresholds).
int minAccelFFamplitudeZ = 27; //default... sent again by host at calibrate sensors.(or send thresholds).
int lastAXdifference = 0;  //what was the last accelerometer difference?
int lastAYdifference = 0;  //what was the last accelerometer difference?
int lastAZdifference = 0;  //what was the last accelerometer difference?
int lastAXFFresult = 0; //what was the last aff result?
int lastAYFFresult = 0; //what was the last aff result?
int lastAZFFresult = 0; //what was the last aff result?
//WIFLY COMMUNICATION WITH ARDUINO
//heartbeat
int heartRate = 888; //heartbeat delay in milliseconds.
unsigned long deadBeat; //this is theTime plus the heartRate delay. If no HB signal is received before deadBeat time is up, consider connection lost and go into errorHandling.
unsigned long flatlinedAt;
unsigned long timeToWiflyReset;
int flatlineWiflyResetDelay = 27000;
boolean resetAfterFlatline = false;
//register availability (applies to specific functions, to stop overflowing the buffer). 
boolean rCRV = false; // this determines if certain functions are allowed to send data to the SPI register.
unsigned long cSW; //time until another variable can be sent to the register.
int cSD = 8; //the delay, in milliseconds, before another character can be sent to the SPI register.
//states
boolean flatline = false;
int wFFC = 0; //wait for first connection, counter.
boolean icM = false; //Initial Connection Made? Have we connected to an AP?
boolean commandMode = false;   //is the Wifly presently in command mode?
boolean started = false; //did we receive the start command, and are now started? (will change if connection is lost or started is received again, or if there's an error.).
boolean escOn = false; //same as started, but for ESC. Is it on?
boolean autoCamera = false; //does the camera movement follow the steering?
boolean settingsReceived = false;
boolean autoCameraProportional = false;
//data
int sCS = 0; //settings checksum.
char newInsCharArray[20]; //new instruction char array... stores the instruction.
int iC = 0; //instruction counter, for the newInsCharArray (new instruction char array).
String newInstruction;
int cIs = 0; //is the new command an integer, a no value command, or a float? (0,1,2 respectively).
//command counter variables
//I use these to count certain instructions I send more than once to activate.
//I do this when certain functions get set accidently by extraneous data coming
//from the SPI uart.
int CCraf = 0;   //command counter, reset after flatline.
//TIME
unsigned long theTime = millis(); //this will be equated to millis();
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
/////////////////////////////////////////////
//                                         //
//          Arduino Setup                  //
//                                         //
/////////////////////////////////////////////
//Setup Routine:
void setup() {
 Serial.begin(9600);
 //Serial.println(&quot; &quot;);
 Serial.print(&quot;WIFLY Arduino Mini v&quot;);
 Serial.print(AWMV);
 Serial.println(&quot; &quot;);
 Serial.println(&quot;--------------------------------------&quot;);  
 Serial.println(&quot;Initialize SpiSerial.&quot;);
 SpiSerial.begin();
 Serial.println(&quot;Connected to SPI UART.&quot;);
 Serial.println();
&nbsp;
  //pinMode(pinOpto1, OUTPUT); //SERVO opto-isolator pin set to OUTPUT
  pinMode(pinOpto, OUTPUT); //ESC opto-isolator pin set to OUTPUT
  //digitalWrite(pinOpto1, LOW); //turn off SERVOS
  digitalWrite(pinOpto, LOW); //turn off ESC
&nbsp;
  pinMode(pinInfoLED, OUTPUT); //information LED pin set to OUTPUT.
  digitalWrite(pinInfoLED, LOW); //information LED off.
&nbsp;
  theCamera.attach(3);  //associates theCamera with indicated pin.
  theGas.attach(5);  //associates theGas with indicated pin.
  theWheel.attach(6);  //associates theWheel with indicated pin.
&nbsp;
  theCamera.write(cameraCentre);
  theWheel.write(wheelCentre);
  theGas.write(throttleNeutral);
  //sBearing = wheelCentre; //initialize sBearing
&nbsp;
  if(requireFirstContact == true) {
    icM = false;
  } else {
    icM = true;
  }
&nbsp;
  if(performSetup == true) {
    //configure wifly
    configureWifly();
  } else {
    //do nothing
  }
&nbsp;
  //Serial.println(&quot;DONE ARDUINO SETUP&quot;);   
}
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
//////////////////////////////////////
//                                  //
//          ERROR HANDLING          //
//                                  //
//////////////////////////////////////
&nbsp;
void errorHandle(String theError)
{
  //do nothing for the time being.
  if(theError == &quot;flatline&quot;) {
    if(throttleSetting &gt; (throttleNeutral + 10)) {
      throttle(throttleFullReverse);
      delay(777);
    }
    neutral();
    if(escOn == true) {
      ESC();
    }
    if(started == true) {
      start();
      flatline = true;
      flatlinedAt = millis();
      timeToWiflyReset = flatlinedAt + flatlineWiflyResetDelay;
      Serial.println(&quot;Flatlined!&quot;);
    }
&nbsp;
  }
}
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
////////////////////////////////////////////////////
//                                                //
//     Update Time Sensitive Functions (uTSF)     //
//     (Sensor checks, etc)                       //
//                                                //
////////////////////////////////////////////////////
void checkACCEL1() {
  theTime = millis();
  if(theTime &gt; accelCheckTime) {
     int accelX = analogRead(pinAccel1X);    // read accel's X value
     int accelY = analogRead(pinAccel1Y);    // read accel's Y value
     int accelZ = analogRead(pinAccel1Z);    // read accel's Z value
     //minAccelFFamplitude
     int accelXaverage = 0;
     int accelYaverage = 0;
     int accelZaverage = 0;
     accel1avgXarray[accel1counter] = accelX; //put X reading into array
     accel1avgYarray[accel1counter] = accelY; //put Y reading into array
     accel1avgZarray[accel1counter] = accelZ; //put Z reading into array
     accel1counter++;
     if(accel1counter &gt; accelAVGarraySize) {
       accel1counter = 0;
     }
     for(int a = 0; a&lt;accelAVGarraySize; a++) {
         //add together, to update average...
         accelXaverage = accelXaverage + accel1avgXarray[a];
         accelYaverage = accelYaverage + accel1avgYarray[a];
         accelZaverage = accelZaverage + accel1avgZarray[a];
     }
     accelXaverage = accelXaverage / accelAVGarraySize;
     accelYaverage = accelYaverage / accelAVGarraySize;
     accelZaverage = accelZaverage / accelAVGarraySize;
&nbsp;
     //now find out if there's enough of a disturbance to report...
     int aXdifference = 0;
     int aYdifference = 0;
     int aZdifference = 0;
     //determine if X reading is out of range
     int accelUnder = accelXaverage - minAccelFFamplitudeX;
     int accelOver = accelXaverage + minAccelFFamplitudeX;
     if(accelX &lt;= accelUnder) {
       aXdifference = accelXaverage - accelX;
     } else if (accelX &gt;= accelOver) {
       aXdifference = accelX - accelXaverage;
     }
     //determine if Y reading is out of range
     accelUnder = accelYaverage - minAccelFFamplitudeY;
     accelOver = accelYaverage + minAccelFFamplitudeY;
     if(accelY &lt;= accelUnder) {
       aYdifference = accelYaverage - accelY;
     } else if (accelY &gt;= accelOver) {
       aYdifference = accelY - accelYaverage;
     }
     //determine if Z reading is out of range
     accelUnder = accelZaverage - minAccelFFamplitudeZ;
     accelOver = accelZaverage + minAccelFFamplitudeZ;
     if(accelZ &lt;= accelUnder) {
       aZdifference = accelZaverage - accelZ;
     } else if (accelZ &gt;= accelOver) {
       aZdifference = accelZ - accelZaverage;
     }
&nbsp;
     //Serial.print(&quot;accelZ: &quot;);
     //Serial.println(accelZ);
     //delay(500);
&nbsp;
     boolean bufferLoaded = false;
     //X
     if(aXdifference != lastAXdifference) {
       //the affXresult has changed, send to host.
       ptb(0x3C); //print &quot;&lt;&quot; to spi uart, for X
       wSPIintToChar(aXdifference);
       bufferLoaded = true;
       lastAXdifference = aXdifference;
     }
     //Y
     if(aYdifference != lastAYdifference) {
       //the affZresult has changed, send to host.
       ptb(0x3E); //print &quot;&gt;&quot;, for Y
       wSPIintToChar(aYdifference);
       bufferLoaded = true;
       lastAYdifference = aYdifference;
     }
     //Z
     if(aZdifference != lastAZdifference) {
       //the affZresult has changed, send to host.
       ptb(0x5E); //print &quot;^&quot;, for Z.
       wSPIintToChar(aZdifference);
       bufferLoaded = true;
       //Serial.println(aZdifference);
       lastAZdifference = aZdifference;
     }
     if(bufferLoaded) {
       fb(); //flush buffer
     }
     accelCheckTime = theTime + accelCheckDelay;
  }
}
void heartbeat()
{
  theTime = millis();
  deadBeat = theTime + heartRate;
  //Serial.println(&quot;BEAT&quot;);
}
void checkHeart() 
{
  if(started == true) {
    theTime = millis();
    if(theTime &gt; deadBeat) {
      errorHandle(&quot;flatline&quot;);  
    }
  } else {
    //started is not equal to true.
    //did we flatline?
    if(flatline == true) {
        theTime = millis();
        if((theTime &gt; timeToWiflyReset) &amp;&amp; resetAfterFlatline) {
          //in development...
          Serial.println(&quot;Attempting to resuscitate connection...&quot;);
          wiflyReconnectToAP();
          //wiflySuddenReboot();
        }
    }
  }
}
void uTSF()
{
  checkHeart();
  if(FFsendACCELDATA &amp;&amp; FFaccelsCalibrated) {
    checkACCEL1();
  }
}
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
/////////////////////////////////////////////
//                                         //
//            DATA MANIPULATION            //
//                                         //
/////////////////////////////////////////////
&nbsp;
//delegate received command.
void delegate(String wtd, char* tVc) {  //tVc = the float char
  float tVf = 0.0;  //the value, float
  int tV = 0;     //integer
&nbsp;
  if(cIs == 0) {
    //the value needs ot be an integer. most cases.
    tV = atoi(tVc);  //turn char array into an integer.
  } else if (cIs == 1) {
    //there is no accompanying value
    //do nothing.
  } else if (cIs == 2) {
    //the value must be a float
    tVf = atof(tVc); //turn char array into a float.
  }
  //most important first:
  if(wtd == &quot;B&quot;) {
      //first tier.
      heartbeat();
      //Serial.println(&quot;Heartbeat!&quot;);
  } else {  //so, not a heartbeat.
    //second tier.
    if(wtd == &quot;W&quot;) {
      //steer
      wheel(tV);
    } else if (wtd == &quot;T&quot; &amp;&amp; escOn == true) {
      //throttle
      throttle(tV);
    } else if(wtd == &quot;L&quot;) {
      //if(autoCamera) {
        //theTime = millis();
        //cMMtimeout = theTime + cMMdelay; //camera manual move timeout, theTime offset by camera manual move delay.
      //}
      if(!autoCamera) {
        camera(tV);
      }
    } else { 
      //third tier.
      if (wtd == &quot;A&quot;) {
        ESC();
      } else if(wtd == &quot;S&quot;) {
        start();
      } else if (wtd == &quot;R&quot;) {
        //set the steering range.
        //Serial.println(&quot;SET STEERING RANGE&quot;);
        steeringRange = tV;
        sCS++;
        //Serial.print(steeringRange);
        //Serial.println(&quot;&quot;);
      } else if (wtd == &quot;V&quot;) {
        //set the wheelCentre
        //Serial.println(&quot;SET WHEEL CENTRE&quot;);
        wheelCentre = tV;
        sCS++;
        //Serial.print(wheelCentre);
        //Serial.println(&quot;&quot;);
      } else if (wtd == &quot;h&quot;) {
        Serial.print(&quot;autoCamera: &quot;);
        if(!autoCamera &amp;&amp; settingsReceived) {
          autoCamera = true;
          ptb(0x68);  //h
          ptb(0x31);  //indicate on.
          fb();
          Serial.println(&quot;ON&quot;);
        } else {
          autoCamera = false;
          ptb(0x68);  //h
          ptb(0x30);  //indicate off.
          fb();
          Serial.println(&quot;OFF&quot;);
        }
      } else if (wtd == &quot;a&quot;) {
        Serial.println(&quot;SET autoCameraRange: &quot;);
        autoCameraRange = tV;
        sCS++;
        Serial.println(cameraCentre);
      } else if (wtd == &quot;l&quot;) {
        Serial.println(&quot;SET Camera Centre: &quot;);
        sCS++;
        cameraCentre = tV;
        Serial.println(cameraCentre);
      } else if (wtd == &quot;x&quot;) {
        //Serial.println(&quot;SET ACCELEROMETER MINIMUM FF AMPLITUDE THRESHOLD X&quot;);
        minAccelFFamplitudeX = tV;
        sCS++;
        //Serial.println(minAccelFFamplitudeX);
      } else if (wtd == &quot;y&quot;) {
        //Serial.println(&quot;SET ACCELEROMETER MINIMUM FF AMPLITUDE THRESHOLD Y&quot;);
        minAccelFFamplitudeY = tV;
        sCS++;
        //Serial.println(minAccelFFamplitudeY);
      } else if (wtd == &quot;z&quot;) {
        //Serial.println(&quot;SET ACCELEROMETER MINIMUM FF AMPLITUDE THRESHOLD Z&quot;);
        minAccelFFamplitudeZ = tV;
        sCS++;
        //Serial.println(minAccelFFamplitudeZ);
      } else if (wtd == &quot;~&quot; &amp;&amp; started == false) {
        //calibrate accelerometer rest ranges
        if(!started) {
          Serial.println(&quot;Calibrating Accelerometer&quot;);
          calibrateAccelerometer();
        }
      } else if (wtd == &quot;M&quot;) {
        //we want pots data.
        Serial.println(&quot;Settings checksum...&quot;);
        if(tV == 0) {
          Serial.println(&quot;Cleared.&quot;);
          sCS = tV;
        } else if (tV &gt; 0) {
          Serial.print(&quot;Comparing, sending to HOST. sCS = &quot;);
          Serial.println(tV);
          ptb(0x4D);  //M
          wSPIintToChar(sCS);
          fb();
        }
      } else if (wtd == &quot;m&quot;) {
        //settings checksum checked out.
        Serial.println(&quot;Checksum Confirmed. Settings Received.&quot;);
        settingsReceived = true;
      } else if (wtd == &quot;X&quot;) {
        //we want acceleration data.
        if(FFsendACCELDATA &amp;&amp; FFaccelsCalibrated) {
          FFsendACCELDATA = false;
          //send ACK
          ptb(0x58);  //X
          ptb(0x30);  //indicate off.
          fb();
        } else if (FFaccelsCalibrated) {
          Serial.println(&quot;SEND FF ACCELERATION DATA&quot;);
          FFsendACCELDATA = true;
          //send ACK
          ptb(0x58);  //X
          ptb(0x31); //indicate on.
          fb();
        }
      } else if (wtd == &quot;Z&quot; &amp;&amp; started == false) {
        //set pot defaults.
        if(CCraf &lt; 2) {
          CCraf++;
        } else {
          CCraf = 0;
          if(resetAfterFlatline) {
            resetAfterFlatline = false;
            ptb(0x5A); //Z
            ptb(0x30); //indicate off.
            fb();
          } else if (!resetAfterFlatline) {
            resetAfterFlatline = true;
            ptb(0x5A); //Z
            ptb(0x31); //indicate on.
            fb();
          }
        }
      } else {
        //clear counted commands to avoid unexpected results
        CCraf = 0; 
        //CCpotvals = 0;
      }
    }
  }  //end of not a heartbeat if
&nbsp;
  //Serial.print(wtd);
  //Serial.println(tV);
}
//interpret received data
void interpret(char c)
{
  String iD = (String)c; //incoming data equated to c.
  //Serial.println(iD);
  if((iD != &quot;0&quot;) &amp;&amp; (iD != &quot;1&quot;) &amp;&amp; (iD != &quot;2&quot;) &amp;&amp; (iD != &quot;3&quot;) &amp;&amp; (iD != &quot;4&quot;) &amp;&amp; (iD != &quot;5&quot;) &amp;&amp; (iD != &quot;6&quot;) &amp;&amp; (iD != &quot;7&quot;) &amp;&amp; (iD != &quot;8&quot;) &amp;&amp; (iD != &quot;9&quot;) &amp;&amp; (iD != &quot;.&quot;)) {
    if(newInstruction != &quot;&quot;) {
      delegate(newInstruction, newInsCharArray);    //send instruction to be delegated.
      //Serial.println(newInstruction);
    }
    newInstruction = iD;
    cIs = 1;  
    iC = 0;                         //set instruction counter to zero.
    memset(newInsCharArray,0,sizeof(newInsCharArray)); //clear char array.
  } else if (newInstruction != &quot;&quot;) {
    //accumulate contents/details of new instruction.
    newInsCharArray[iC] = c;
    iC++;
    //there are accompanying values, set cls to 0
    if(cIs == 1) {
      cIs = 0;
    }
    if(cIs == 1 &amp;&amp; iD == &quot;.&quot;) {
      //accompanying value will be a float
      cIs = 2;
    }
    //Serial.print(c);
  } else {
    //something's happened that shouldn't have.
    Serial.println(&quot;ComSeqErr&quot;);
    errorHandle(&quot;ComSeqErr&quot;);
  }
&nbsp;
}
void wSPIintToChar(int tpd) {
  if(rCRV == true) {
    char tpdc[12];  //was six
    itoa(tpd, tpdc, 10);  //turn the integer into an ascii string/char array.
    int tpdcl = strlen(tpdc);  //how long is the string?
    for(int ttt = 0; ttt &lt; tpdcl; ttt++) {
      //Serial.println(tpdc[ttt]);
      ptb(tpdc[ttt]);
    }
  }
}
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
/////////////////////////////////////////////////////////////
//                                                         //
//                    COMMAND   FUNCTIONS                  //
//                                                         //
/////////////////////////////////////////////////////////////
void neutral()
{
  throttle(throttleNeutral);
}
void ESC() {
    //turn on the ESC or turn it off.
    if(escOn == false &amp;&amp; started == true) {
      ptb(0x41);
      digitalWrite(pinOpto, HIGH); //ESC ON
      escOn = true;
      Serial.println(&quot;ESC is on&quot;);
    } else if(escOn == true) {
      ptb(0x61);
      digitalWrite(pinOpto, LOW); //ESC OFF
      escOn = false;
      Serial.println(&quot;ESC is off&quot;);
    }
    fb(); //flush buffer
}
void camera(int dir)
{
  theCamera.write(dir);
}
void wheel(int bearing)
{
  theWheel.write(bearing);
&nbsp;
  //cBearing = bearing;
  if(autoCamera) {
    //theTime = millis();
    //if(theTime &gt; cMMtimeout) {
      float cameraPoint = 0.0;
      int cameraPointSet = 0;
      float percentToMove = 0.0;
      if(bearing &lt; wheelCentre) {
        //turning left
        percentToMove = ((float)wheelCentre - (float)bearing) / (float)steeringRange;
        if(autoCameraProportional) {
          percentToMove = pow(percentToMove,2);
        } else {
          //do nothing.
        }
        cameraPoint = (float)cameraCentre + ((float)autoCameraRange * percentToMove);
      } else if (bearing &gt; wheelCentre) {
        percentToMove = ((float)bearing - (float)wheelCentre) / (float)steeringRange;
        if(autoCameraProportional) {
          percentToMove = pow(percentToMove,3);
        } else {
          //do nothing.
        }
        cameraPoint = (float)cameraCentre - ((float)autoCameraRange * percentToMove);
      } else {
        //camera centred.
        cameraPoint = (float)cameraCentre;
      }
      cameraPointSet = cameraPoint; //float to int.
      //Serial.print(&quot;cameraPointSet: &quot;);
      //Serial.println(cameraPointSet);
      camera(cameraPointSet);
    //}
  }
&nbsp;
}
void throttle(int power)
{
  throttleSetting = power;
  theGas.write(power);
}
void start() {
    if(started == false) {
      if(settingsReceived) {
        //start up, send ACK.
        started = true;
        ptb(0x53);
        Serial.println(&quot;STARTED&quot;);
        heartbeat(); //start the heart.
        flatline = false;
      } else {
        //request user send settings
        ptb(0x70);  //ask for settings (&quot;p&quot;);
        fb();       //flush buffer
      }
    } else if (started == true) {
      //shut down, send ACK.
      started = false;
      ptb(0x73);
      digitalWrite(pinOpto, LOW); //ESC OFF
      escOn = false;
      Serial.println(&quot;STOPPED&quot;);
    }
    fb();
}
void calibrateAccelerometer() {
  //set accelerometer at-rest values...
  int theXaverage = 0;
  int theYaverage = 0;
  int theZaverage = 0;
  for(int a = 0; a &lt; accelAVGarraySize; a++) {
    //initialize array
    accel1avgXarray[a] = analogRead(pinAccel1X);
    theXaverage = theXaverage + accel1avgXarray[a];
    accel1avgYarray[a] = analogRead(pinAccel1Y);
    theYaverage = theYaverage + accel1avgYarray[a];
    accel1avgZarray[a] = analogRead(pinAccel1Z);
    theZaverage = theZaverage + accel1avgZarray[a];
&nbsp;
    boolean showme = false;
    if(showme) {
      Serial.print(&quot;accel1avgXarray[&quot;);
      Serial.print(a);
      Serial.print(&quot;] = &quot;);
      Serial.print(accel1avgXarray[a]);
&nbsp;
      Serial.print(&quot;, accel1avgYarray[&quot;);
      Serial.print(a);
      Serial.print(&quot;] = &quot;);
      Serial.print(accel1avgYarray[a]);
&nbsp;
      Serial.print(&quot;, accel1avgZarray[&quot;);
      Serial.print(a);
      Serial.print(&quot;] = &quot;);
      Serial.println(accel1avgZarray[a]);
    }
&nbsp;
    delay(15);
  }
  Serial.println(&quot;BEFORE DIVISION:&quot;);
  Serial.print(&quot;theXaverage = &quot;);
  Serial.print(theXaverage);
  Serial.print(&quot;, theYaverage = &quot;);
  Serial.print(theYaverage);
  Serial.print(&quot;, theZaverage = &quot;);
  Serial.println(theZaverage);
&nbsp;
&nbsp;
  theXaverage = theXaverage / accelAVGarraySize;
  theYaverage = theYaverage / accelAVGarraySize;
  theZaverage = theZaverage / accelAVGarraySize;
&nbsp;
  Serial.println(&quot;AFTER DIVISION:&quot;);
  Serial.print(&quot;theXaverage = &quot;);
  Serial.print(theXaverage);
  Serial.print(&quot;, theYaverage = &quot;);
  Serial.print(theYaverage);
  Serial.print(&quot;, theZaverage = &quot;);
  Serial.println(theZaverage);
&nbsp;
  ptb(0x47);  //tell homebase calibration is complete.
  fb();       //flush buffer
&nbsp;
  FFaccelsCalibrated = true;
}
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
/////////////////////////////////////////////
//                                         //
//        WIFLY  GENERAL COMMANDS          //
//                                         //
/////////////////////////////////////////////
//general command definitions:
char EC = 0x0d;  //(carriage return for entering commands)
char ED = 0x24;  //$
char FBC = 0xd;  //flush buffer command, carriage return.
//general command functions:
void exCom() {
  //execute command on WIFLY when in Command Mode.
 SpiSerial.print(EC, BYTE);  //execute command 
}
void fb()
{
  //FLUSH BUFFER (sends buffer contents to host).
  //a carriage return character (hex 0xd) flushes the WIFLYs buffer and sends the msg
  theTime = millis();
  if(theTime &gt; cSW) {
    //it's ok to send now.
    SpiSerial.print(FBC, BYTE);
    rCRV = false; //register can no longer receive values.
    //set wait period!
    cSW = theTime + cSD;
  }
}
void stSPI(String s) {
  SpiSerial.print(s); //print string s to SPI serial
}
void enterCommandMode()
{ 
  for(int i = 0; i &lt;= 2; i++) {
     SpiSerial.print(ED, BYTE);
  }
  rpSPI();
  commandMode = true;
}
void quitCommandMode()
{
  stSPI(&quot;exit&quot;); //Exit Command Mode
  exCom();  //execute command
  delay(333);
  rpSPI();
  commandMode = false;
}
void rebootWIFLY() {
  Serial.println(&quot;Rebooting...&quot;);
  delay(1333);
  //this is performed when already in command mode
  stSPI(&quot;reboot&quot;); //Reboot command
  exCom();  //execute command 
  delay(2000);
  commandMode = false;
}
void wiflySuddenReboot()
{
  //performed if not already in command mode.
  enterCommandMode();
  delay(666);
  exCom();  //execute command, in case there's anything there...
  rebootWIFLY();
  rpSPI();
  delay(666);
}
void wiflyReconnectToAP()
{
  //performed if not already in command mode.
  enterCommandMode();
  delay(500);
  stSPI(&quot; &quot;);
  exCom();  //execute command, in case there's anything there...
  delay(333);
  rpSPI();
  stSPI(&quot;leave&quot;);
  exCom();  //execute command
  delay(3500);
  rpSPI();
  stSPI(&quot;join&quot;);
  delay(3500);
  rpSPI();
  delay(3500);
  rpSPI();
  quitCommandMode();
  quitCommandMode();
  delay(666);
  rpSPI();
  rpSPI();
  rpSPI();
  rpSPI();
  rpSPI();
  rpSPI();
  rpSPI();
  rpSPI();
  rpSPI();
  rpSPI();
  theTime = millis();
  timeToWiflyReset = theTime + flatlineWiflyResetDelay;
}
&nbsp;
&nbsp;
&nbsp;
/////////////////////////////////////////////
//                                         //
//         WIFLY SETUP and STATUS          //
//                                         //
/////////////////////////////////////////////
//wifly status manipulation/checking
void waitForFirstConnection()
{
  if(commandMode) {
    String cm = &quot;show connection&quot;;
    SpiSerial.print(cm); //show connection status
    exCom();
    delay(666);
    int responseLength = 3; //number of digits of the response minus one, since 0 is counted.
    int i = 0;
    int ii = 0;
    int iStart = cm.length() + 3;
    int iEnd = iStart + 3;
    //Serial.println(iStart);
    //Serial.println(iEnd);
    char c;
    char w[8];
    while(SpiSerial.available() &gt; 0) {
      c = SpiSerial.read();
      if((i&gt;=iStart) &amp;&amp; (i&lt;=iEnd)) {
        w[ii] = c;
        ii++;
      }
      i++;
    }
    w[4] = 0x0; //nullify the last character in the string so the comp knows where the end of the string is.
    String iString1 = w;
    if(iString1 == &quot;8130&quot;) {
     Serial.println(&quot;First AP Association Established. Quitting command mode...&quot;);
     quitCommandMode();
     Serial.println(&quot; &quot;);
     Serial.println(&quot;Entering Main Loop.&quot;);
     icM = true;
    }
    rpSPI();
    delay(1111);
  } else {
    enterCommandMode();
    delay(1111);
    rpSPI();
  }
}
void configureWifly()
{
    delay(333);
    Serial.println(&quot;Configuring Wifly...&quot;);
    //SET UP WIFLY
&nbsp;
    //enter command mode (prints $$$).
    enterCommandMode();
&nbsp;
    rpSPI();
&nbsp;
    stSPI(&quot; &quot;);   //purposely sabotage the command line, then
    exCom();      //execute command
                  //that would happen if the arduino was rebooted before it had finished issuing commands to WIFLY
&nbsp;
    //set dhcp mode
    stSPI(&quot;set ip dchp 1&quot;); //set DHCP mode
    exCom();      //execute command
&nbsp;
    rpSPI();
&nbsp;
    //set the SSID
    stSPI(&quot;set wlan ssid &quot;); //set ssid
    stSPI(ssid); //set ssid
    exCom();      //execute command
&nbsp;
    rpSPI();
&nbsp;
    stSPI(&quot;set wlan phrase &quot;); //set password
    stSPI(phrase); //set ssid
    exCom();      //execute command
&nbsp;
    rpSPI();
&nbsp;
    stSPI(&quot;set ip host &quot;); //set the host IP address.
    stSPI(hostIP);
    exCom();      //execute command
&nbsp;
    rpSPI();
&nbsp;
    stSPI(&quot;set ip localport &quot;); //set the listen port
    stSPI(listenPort);
    exCom();      //execute command
&nbsp;
    rpSPI();
&nbsp;
    stSPI(&quot;set ip remote &quot;); //set the send port
    stSPI(sendPort);
    exCom();      //execute command
&nbsp;
    rpSPI();
&nbsp;
    stSPI(&quot;save&quot;); //save settings
    exCom();      //execute command
&nbsp;
    rpSPI();
&nbsp;
    Serial.println(&quot; &quot;);
    Serial.println(&quot;Configured, Saved, Now Reboot:&quot;);
    Serial.println(&quot; &quot;);
&nbsp;
    //reboot the wifly. this function has a delay set within.
    rebootWIFLY();
    rpSPI();
    Serial.println(&quot;Wifly is Ready&quot;);
}
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
///////////////////////////////////////////
//                                       //
//       WIFLY SPI COMMUNICATION         //
//                                       //
//                                       //
///////////////////////////////////////////
void rpSPI()
{
 delay(500);
 while(SpiSerial.available() &gt; 0) {
    Serial.print(SpiSerial.read(), BYTE);
  }
  Serial.println(&quot; &quot;);
}
void ptb(char c)
{
  //prints to SpiSerial (fills up the buffer).
  theTime = millis();
  if(theTime &gt; cSW) {
    if((c == 0x3C) || (c == 0x3E) || (c == 0x5E) || (c==0x4D) || (c==0x4E) || (c==0x68) || (c==0x46)) {
      //we have received a command that requires a value to be sent.
      //therefore, it is ok for the register to receive values.
      //it's MEANT FOR spiIntToChar function... so we don't issue values to the register if there's no representing character.
      rCRV = true;
    }
    //0x4b = K. 0x41 = A. 0x61 = a. 0x53 = S. 0x73 = s.
    //char c = 0x4b; 
    SpiSerial.print(c, BYTE);
  }
}
void listenForCommands()
{
  //read by arduino when receiving commands.
  while(SpiSerial.available() &gt; 0) {
    char c = SpiSerial.read();
    //Serial.print(c);
    interpret(c);  //send char to the interpret function.
  }
}
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
///////////////
//   LOOP ////
//////////////
&nbsp;
void loop() 
{ 
  if(icM) {
    listenForCommands(); //Listen for commands from Host received by Wifly.
    uTSF(); //run functions that update time-sensitive variables...
  } else {
    waitForFirstConnection();
  }
}</pre></td></tr></table></div>

</div>
]]></content:encoded>
			<wfw:commentRss>http://www.blairkelly.ca/2012/04/20/arduino-wifly-mini-arduino-code/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Arduino Wifly Mini: Processing Code</title>
		<link>http://www.blairkelly.ca/2012/04/20/arduino-wifly-mini-processing-code/</link>
		<comments>http://www.blairkelly.ca/2012/04/20/arduino-wifly-mini-processing-code/#comments</comments>
		<pubDate>Fri, 20 Apr 2012 19:25:05 +0000</pubDate>
		<dc:creator>blair</dc:creator>
				<category><![CDATA[Arduino]]></category>
		<category><![CDATA[Code]]></category>
		<category><![CDATA[CodeSingle]]></category>
		<category><![CDATA[Multimedia]]></category>
		<category><![CDATA[arduino]]></category>
		<category><![CDATA[code]]></category>
		<category><![CDATA[Mini]]></category>
		<category><![CDATA[Processing]]></category>
		<category><![CDATA[Wifly]]></category>

		<guid isPermaLink="false">http://www.blairkelly.ca/?p=2842</guid>
		<description><![CDATA[Code for the Arduino Wifly Mini, Processing side.]]></description>
			<content:encoded><![CDATA[<p>Here is the Processing script for the <a href="/2012/04/09/arduino-wifly-mini/">Arduino Wifly Mini</a> project. Please note this code was written in Processing version 1.5.1.</p>
<div id="codecontainer">

<div class="wp_syntax"><table><tr><td class="line_numbers"><pre>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
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
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
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
</pre></td><td class="code"><pre class="language" style="font-family:monospace;">//WIFLY Mini Multicontroller Processing Sketch
//VERSION 1.27
//Controls WIFLY Mini with Playstation 3 Controller (Mac OS X), XBOX 360 Controller (Mac OS X &amp; Windows 7),
//and Logitech G27 Steering Wheel (Windows 7), Logitech F510 Rumblepad (Windows, both Dinput and Xinput modes).
&nbsp;
//IMPORT STATEMENTS
import hypermedia.net.*;
import procontroll.*;
import java.io.*;
&nbsp;
//VARIABLES
//CONTROLLER IN USE:
int usingController = 1;  //0 = PS3 for Mac, 
                          //1 = Xbox360 for Windows 7, 2 = Xbox360 controller for MacOS
                          //3 = G27 Steering Wheel for Windows 7
                          //4 = Logitech F510 rumblepad, xInput, 5=Same controller, directInput
//STEERING
int wheelCentreDefault = 96; //was 96
float wheelMaxTravelDefault = 34.0; //was 36. 42 is the max so far. is a float value because it is multiplied by a float value.
int wheelCentre = wheelCentreDefault;
float wheelMaxTravel = wheelMaxTravelDefault;
int steeringRange = (int)wheelMaxTravel;  //just wheelmaxtravel in integer form.
int steeringLevel = 0; //indicates steering range/tier... wide/narrow.
int wheelSetting = wheelCentreDefault;
int lastWheelSetting = wheelCentreDefault;
float wheelLastReading = 0;
float wheelCurrentReading = 0;
float wheelSettingFloat;
float wheelMovementMin; //the minimum change the wheel must experience to recalculate new instruction. SET IN CONTROLLER MAP
float wheelHardValue;  //Set in controllmap. if under this (when reading is negative) or over this (when reading is positive) value, go hard left or right.
float wheelStickDeadZone;    //if using a stick, this (negative and postive) are boundaries for wheelCentre to activate. set in controller map
float wheelDeadZone;    // deadzone for wheel. set in controller map
float wheelPOW;  //proportional steering setting of wheel... sometimes useful with stick steering. set in controller maps.
//THROTTLE AND BRAKE
float throttleMax = 120;
float numberOfGears = 6;
float throttleForMaxDefault = (throttleMax / 6);   //120 max.
float throttleRevMaxDefault = 20;  //20 max.
float throttleMaxForward = throttleForMaxDefault;
float throttleMaxReverse = throttleRevMaxDefault / 2;
int throttleNeutral = 50;  //don't touch! (set at 50!)
float throttleCurrentReading = 0;
float throttleLastReading = 0;
float throttleSettingFloat = 0;
int throttleSetting = throttleNeutral;
int lastThrottleSetting = 0;
float throttleStickDeadzone; //set in controllermaps. what's the deadzone when the throttle is a stick?
float reverseCurrentReading = 0;
float pedalDeadZone; //if reading is less than this, pedal is considered not engaged. set in controller map
float pedalMovementMin; //set in control maps
float pedalHardValue; //set in control maps.
//CLUTCH
float clutchCurrentReading;
float clutchThrow; //set in controller map. percentage sliding clutch must be depressed to activate.
//CAMERA
boolean flipCameraStickX = true;  //do you want to flip the x reading of the camera control stick, if there is one?
int cameraCentre = 97;  //in this case higher is more to the left...
float cameraMaxLeft = 176.0;
float cameraMaxRight = 4.0;
int cameraSetting = cameraCentre;
float cameraLastReading = 0;
float cameraCurrentReading = 0;
float cameraSettingFloat;
float cameraLeftRange = (float)cameraCentre - cameraMaxLeft;
float cameraRightRange = cameraMaxRight - (float)cameraCentre;
int cameraStep; //the movement step size the camera's servo will take.
long cameraMoveTime = 0;
int cameraMoveInterval; //number of milliseconds between each camera step. set in controller map.
float cameraMovementMin; //set in controller map. if camera movement is controlled by a stick, the stick has to be moved this much to calculate a new value.
float cameraHardValue; //set in controllermap
float cameraStickDeadZone;  //set in controller map.
float cameraMaxStep; //set in controller map.
float cameraMinStep; //set in controller map.
int autoCameraRange = 27; //how far the camera will travel when autocamera is on. (follows steering stick).
boolean autocameraOn = false;
//FORCE FEEDBACK AND OTHER SENSORS
boolean rumbling = false; //this needs to be made better... are any of the rumblers going?
boolean rumbling0 = false; //this needs to be made better... represents rumbler number one. Is it rumbling or not?
boolean rumbling1 = false; //this needs to be made better... represents rumbler number one. Is it rumbling or not?
boolean rumbling2 = false; //this needs to be made better... represents rumbler number one. Is it rumbling or not?
float rumble0val = 0.0;
float rumble1val = 0.0;
float rumble2val = 0.0;
long timeFFreduce;
int timeFFdelay = 16; //time in ms till the force feedback is reduced.
float feedbackreceived = 0.0;
float feedbacksetting = 0.0;
int minAccelFFamplitudeX = 99; //minimum fluctuation to cause accel feedback X to send from wifly.
int minAccelFFamplitudeY = 20; //minimum fluctuation to cause accel feedback Y to send from wifly.
int minAccelFFamplitudeZ = 30; //was 50 //minimum fluctuation to cause accel feedback Z to send from wifly.
int aDivider = 123; //was 127. this is the divider used by arduino to determine amplitude of FF from Accelerometer... it is arbitrary.
int hasRumblers;        //how many rumblers does the controller have? set in controlmap.
float maxRumbleIntensity;  //set in controllmap.
//timeFFreduce
//TIME
long theTime; //this will be equated to millis();
//Heartbeat Timing
int heartRate = 111;
long beatTimer; //will be equated to theTime plus heartRate;
//Command Timing
int repeatDelay = 23; //number of milliseconds between each command send.
//COMMUNICATIONS
//Address, Ports.
//String ip       = &quot;10.0.1.33&quot;; // The Mini's IP Address
  //String ip       = &quot;192.168.0.33&quot;; // ON DLINK
  String ip       = &quot;192.168.1.3&quot;; // ON HITCH
  //String ip       = &quot;207.164.79.91&quot;; //iPhone, experimental.
  //String ip = &quot;24.141.134.157&quot;; //home base. experimental.
  //String ip = &quot;172.20.10.5&quot;; //experimental
int sendPort        = 8888;        // Destination Port, usually 8888
int listenPort  = 6000;          //port to listen on. usually 6000.
//Command Handling
String comMsg = &quot;&quot;; //the command message sent at regular intervals...
String delayedCommands = &quot;&quot;; //for commands that would otherwise be discarded due to the nature of the processing loop...
int sCS = 0;  //settings checksum
String instruction;  //instruction to send
int commandCounter = 0;            //how many commands have been sent since a heartbeat was last sent?
int commandsSentToHeartbeat = 10;  //how many commands need to be sent before a heartbeat is sent?
//boolean eocReceived = false;;
String instructionDataString = &quot;&quot;;
String aCommand = &quot;&quot;;  //the command received from Arduino.
int aCommandVal = 0;  //value attached to command, if any.
//Commands (to send to ARDUINO).
boolean comDelayRequired = false;
String CsetCamera = &quot;L&quot;;  //camera setting
String CsetCameraCentre = &quot;l&quot;;
String CautoCamera = &quot;h&quot;;
String CsetAutoCameraRange = &quot;a&quot;;
String CsetWheel = &quot;W&quot;;  //wheel setting
String CsetThrottle = &quot;T&quot;;  //throttle setting
String Cstart = &quot;S&quot;;  //Start Command.
String CswitchESC = &quot;A&quot;;  //ESC setting.
String CheartBeat = &quot;B&quot;;  //heartbeat
String CsetSteeringRange = &quot;R&quot;;  //SteeringRange (wheelMaxTravelDefault);
String CsetWheelCentre = &quot;V&quot;;  //wheel centre default.
String CcalibrateSensors = &quot;~&quot;;  //Calibrate Sensors (Steering Pot, Accelerometer, etc).  (tilde).
String CresetOnFlatline = &quot;Z&quot;;  //if the arduino receives this, switches on/off: reset if the heartbeat is lost.
  String CaAminX = &quot;x&quot;; //X axis of accelerometer amplitude minimum command.
  String CaAminY = &quot;y&quot;; //Y axis of accelerometer amplitude minimum command.
  String CaAminZ = &quot;z&quot;; //Z axis of accelerometer amplitude minimum command.
  //String CsPTM = &quot;k&quot;; //steering pot threshold maximum command
  //String CoBD = &quot;l&quot;; //old bearing delay time
  String CsendFFaccels = &quot;X&quot;;  //send accel forcefeedback info
String CSCS = &quot;M&quot;;  //tell arduino to reset settings checksum. Also used by arduino to indicate its settings checksum for verification.
String CsCSgood = &quot;m&quot;; //command tells arduino the settings checksum was good.
//String endOfCommand = &quot;Q&quot;;  //close command
//Vehicle Operation States
boolean started = false; //has the start button been pressed and we received a response?
boolean escOn = false;  //according to this, is the esc on or off?
boolean reversing = false; //is a reverse-power instruction being sent to the ESC?
boolean clutchIn = false; //is the clutch being depressed?
boolean settingsSent = false; //have the settings been successfully sent to the arduino?
int direction = 0; //0 = neutral, 1 = forward, 2 = reverse
int gear = 1; //we start in first gear. Goes up to 6.
//NETWORK DECLARATION
UDP udp;
//UNIVERSAL CONTROLLER DECLARATIONS
ControllIO controll;
ControllDevice controlDevice;  //equated to the device of control, xbox 360/PS3/G27 etc.
//variables (set in controller maps).
//details
boolean hasCoolieHat;   //does the controller have a cooliehat?
boolean coolieIsCoolie; //is the cooliehat treated as a cooliehat, or is it broken out into buttons?
boolean clutchForGears; //do we need to press a clutch in to go up a gear or down?
boolean hasSlidingClutch; //does the controller have a sliding clutch or a button?
boolean hasForRevGear;  //is there a forward/reverse gear method? used to determine if we need to pay attention to &quot;direction&quot;.
boolean hasReversePedal; //the controller has a dedicated reverse pedal. set in controllermaps.
boolean wheelIsStick;   //is the steering controlled by a stick? (affects steering curve).
boolean throttleIsStick; //is throttle a stick or a pedal? affects deadzones used.
boolean hasCameraStick;  //is the camera control on this controller a slider?
boolean hasCameraButtons; //does this controller have buttons that control the camera?
boolean hasCameraCentreButton;  //does this button have a dedicated button for centering a controller?
boolean oneSliderForThrottle; //does the controller have only one slider for throttle (like xbox 360 on windows or g27 with only one pedal?).
//sliders, sticks
ControllSlider sliderWheel;    //equated to the slider (stick, wheel, etc) designated for steering. fairly consistent.
ControllSlider sliderThrottle; //slider for forward acceleration
int throttleBase; //set in controll map. 0 = regular (-1 to 1, rests at 0), 1 = -1 to 1, rests at -1. 2= SPLIT across two padels, rests at 0, range: 1 to -1.
ControllSlider sliderReverse;    //slider for backwards acceleration
int reverseBase; //set in controll map. 0 = regular (-1 to 1, rests at 0), 1 = -1 to 1, rests at -1.
ControllSlider sliderClutch;    //slider for clutch.
int clutchBase; //set in controll map. 0 = regular (-1 to 1, rests at 0), 1 = -1 to 1, rests at -1.
ControllSlider sliderCameraX;  //slider for controlling camera movement, X plane.
//cooliehat (note that the drivers I'm using for xbox 360 controller on mac break the cooliehat out into buttons)
ControllCoolieHat cooliehat;
//Buttons
//cooliehat buttons
boolean cooliehatPressed = false;
boolean cooliePressedOnce = false; //for functions controlled by the cooliehat that need to execute once with each press.
ControllButton coolieUP;
boolean buttonCOOLIEUPpressed = false;
ControllButton coolieDOWN;
boolean buttonCOOLIEDOWNpressed = false;
ControllButton coolieLEFT;
boolean buttonCOOLIELEFTpressed = false;
ControllButton coolieRIGHT;
boolean buttonCOOLIERIGHTpressed = false;
//general buttons
ControllButton buttonStart;  
boolean buttonStartPressed = false;
ControllButton buttonESC;
boolean buttonESCpressed = false;
ControllButton buttonSendSettings;
boolean buttonSendSettingsPressed = false;
ControllButton buttonResetOnFlatline;
boolean buttonResetOnFlatlinePressed = false;
&nbsp;
ControllButton buttonGEARUP;  
boolean buttonGEARUPpressed = false;
ControllButton buttonGEARDOWN;
boolean buttonGEARDOWNpressed = false;
&nbsp;
ControllButton buttonCentreCamera;
boolean buttonCentreCameraPressed = false;
ControllButton buttonAutoCamera;
boolean buttonAutoCameraPressed = false;
ControllButton buttonCameraMoveLeft;
boolean buttonCameraMoveLeftPressed = false;
ControllButton buttonCameraMoveRight;
boolean buttonCameraMoveRightPressed = false;
&nbsp;
ControllButton buttonCLUTCH;
boolean buttonCLUTCHpressed = false; //here mostly for consistency...
&nbsp;
ControllButton buttonForward;     //NOT for acceleration, but for direction indication when using one accelerator in some circumstances.
boolean buttonForwardPressed = false;  
ControllButton buttonReverse;     //NOT for acceleration, but for direction indication when using one accelerator in some circumstances.
boolean buttonReversePressed = false;
&nbsp;
ControllButton buttonGEAR1;     //Example: For Shifter Gear 1 on G27.
boolean buttonGEAR1Pressed = false;
ControllButton buttonGEAR2;     //Example: For Shifter Gear 2 on G27.
boolean buttonGEAR2Pressed = false;  
ControllButton buttonGEAR3;     //Example: For Shifter Gear 3 on G27.
boolean buttonGEAR3Pressed = false;  
ControllButton buttonGEAR4;     //Example: For Shifter Gear 4 on G27.
boolean buttonGEAR4Pressed = false;  
ControllButton buttonGEAR5;     //Example: For Shifter Gear 5 on G27.
boolean buttonGEAR5Pressed = false;  
ControllButton buttonGEAR6;     //Example: For Shifter Gear 6 on G27.
boolean buttonGEAR6Pressed = false;  
ControllButton buttonGEARreverse;     //Example: For Shifter Gear REVERSE on G27.
boolean buttonGEARreversePressed = false;  
&nbsp;
ControllButton buttonCalibrateSensors;   //instructs vehicle to calibrate pots attached to steering
boolean buttonCalibrateSensorsPressed = false;
ControllButton buttonSendFFaccels;   //instructs vehicle to send (or not send) accelerometer feedback...
boolean buttonSendFFaccelsPressed = false;
&nbsp;
&nbsp;
//////////////////////////////////
//                              //
//            Setup             //
//                              //
//////////////////////////////////
void setup() {
  udp = new UDP( this, listenPort );  // create a new datagram connection on port 6000
  //udp.log( true );         // &lt;-- printout the connection activity
  udp.listen( true );           // and wait for incoming message  
&nbsp;
  setController();  //setup controller
&nbsp;
  println(&quot;Setup complete&quot;);
}
&nbsp;
////////////////////////////////////////////
//                                        //
//            Controller Maps             //
//                                        //
////////////////////////////////////////////
void mapPS3mac()
{
  controlDevice = controll.getDevice(&quot;PLAYSTATION(R)3 Controller&quot;);
  printDeviceItems();
  controlDevice.setTolerance(0.05f);  //was 0.5f.
  wheelMovementMin = 0.001;  //wheel has to be moved this much in order to calculate new value.
  wheelStickDeadZone = 0.01;    //stick neutral deadzone
  wheelHardValue = 0.93;
  wheelPOW = 1.0;
  throttleStickDeadzone = 0.05; //throttle neutral deadzone.
  pedalDeadZone = 0.004;
  pedalMovementMin = 0.004;
  pedalHardValue = 0.95;
  cameraMoveInterval = 6; //milliseconds between camera steps.
  cameraMovementMin = 0.01; //if camera movement is controlled by a stick, the stick has to be moved this much to calculate a new value.
  cameraStickDeadZone = 0.07;
  cameraHardValue = 0.90;
  cameraMaxStep = 7;
  cameraMinStep = 4;
  hasCoolieHat = true;    //the controller has a cooliehat.
  coolieIsCoolie = false;  //the cooliehat is broken into buttons.
  clutchForGears = true;  //does the clutch need to be depressed to change gears?
  hasSlidingClutch = false;
  hasForRevGear = false;  //false. used to determine if we should pay attention to &quot;direction&quot;.
  oneSliderForThrottle = true; //one stick, on the left, signifies both directions...
  hasReversePedal = false; //this controller does not have a dedicated reverse pedal.
  wheelIsStick = true;    //steering is controlled by a stick. (as opposed to a wheel, like the G27)
  throttleIsStick = true; //make sure you use stick deadzones.
  hasCameraStick = false;   //is camera control a stick on this controller?
  hasCameraButtons = true;
  hasCameraCentreButton = true;
  hasRumblers = 2;  //controller has rumblers, though I can't get them to work yet.
  maxRumbleIntensity = 1.0;
  throttleBase = 2;  //0 = -1 to 1, rests at 0. 1 = -1 to 1, rests at -1. 2= SPLIT across two padels, rests at 0, range: 1 to -1.
  reverseBase = 0;
  clutchBase = 0;
  //sliders
  sliderWheel = controlDevice.getSlider(2);
  sliderThrottle = controlDevice.getSlider(1);
  //sliderCameraX = controlDevice.getSlider(1);
  //buttons
  buttonStart = controlDevice.getButton(3);      //START BUTTON
  buttonESC = controlDevice.getButton(0);     //SELECT BUTTON
  buttonSendSettings = controlDevice.getButton(3);      //START BUTTON, clutch must be in.
  buttonResetOnFlatline = controlDevice.getButton(0);      //SELECT BUTTON (clutch must be in for this one to work).
&nbsp;
  buttonGEARDOWN = controlDevice.getButton(10);       //LTB
  buttonGEARUP = controlDevice.getButton(11);        //RTB
  buttonCLUTCH = controlDevice.getButton(2);    //RJD
&nbsp;
  buttonCentreCamera = controlDevice.getButton(1);    //LJD (depress to adjust camera trim).
  buttonAutoCamera = controlDevice.getButton(14);    //X. autoCamera ON/OFF
  buttonCameraMoveLeft = controlDevice.getButton(8);       //LEFT PADDLE
  buttonCameraMoveRight = controlDevice.getButton(9);        //RIGHT PADDLE
&nbsp;
  buttonCalibrateSensors = controlDevice.getButton(12);        //TRIANGLE  (clutch must be in. calibrates sensors. Vehicle should be stopped!!)
  buttonSendFFaccels = controlDevice.getButton(12);        //TRIANGLE (when clutch is NOT in).
&nbsp;
  //cooliehat
  //broken into buttons for this controller.
  //(left arrow pad)
  coolieUP = controlDevice.getButton(4);    //cooliehat UP
  coolieDOWN = controlDevice.getButton(6);    //cooliehat DOWN
  coolieLEFT = controlDevice.getButton(7);    //cooliehat LEFT
  coolieRIGHT = controlDevice.getButton(5);    //cooliehat RIGHT 
}
void mapXBOXwindows()
{
  controlDevice = controll.getDevice(&quot;XBOX 360 For Windows (Controller)&quot;);
  //printDeviceItems();
  controlDevice.setTolerance(0.05f);  //was 0.5f.
  wheelMovementMin = 0.001;  //wheel has to be moved this much in order to calculate new value.
  wheelStickDeadZone = 0.005;    //stick neutral deadzone
  wheelHardValue = 0.93;
  wheelPOW = 1.0;
  pedalDeadZone = 0.004;
  pedalMovementMin = 0.002;
  pedalHardValue = 0.95;
  cameraMoveInterval = 6; //milliseconds between camera steps.
  cameraMovementMin = 0.01; //if camera movement is controlled by a stick, the stick has to be moved this much to calculate a new value.
  cameraStickDeadZone = 0.2;
  cameraHardValue = 0.90;
  cameraMaxStep = 6;
  cameraMinStep = 1;
  hasCoolieHat = true;    //the controller has a cooliehat.
  coolieIsCoolie = true;  //the cooliehat is broken into buttons.
  clutchForGears = true;  //does the clutch need to be depressed to change gears?
  hasSlidingClutch = false;
  hasForRevGear = false;  //false. used to determine if we should pay attention to &quot;direction&quot;.
  oneSliderForThrottle = true; //even though there are two pedals, on this controller in windows one slider represents both pedals.
  hasReversePedal = false; //this controller does not have a dedicated reverse pedal.
  wheelIsStick = true;    //steering is controlled by a stick.
  throttleIsStick = false; //make sure you use pedal deadzones.
  hasCameraStick = true;   //is camera control a stick on this controller?
  hasCameraButtons = true;
  hasCameraCentreButton = true;
  hasRumblers = 2;  //controller has rumblers, though I can't get them to work.
  maxRumbleIntensity = 1.0;
  throttleBase = 2;  //0 = -1 to 1, rests at 0. 1 = -1 to 1, rests at -1. 2= SPLIT across two padels, rests at 0, range: 1 to -1.
  reverseBase = 0;
  clutchBase = 0;
  //sliders
  sliderWheel = controlDevice.getSlider(3);
  sliderThrottle = controlDevice.getSlider(4);
  sliderCameraX = controlDevice.getSlider(1);
  //stick = new ControllStick(sliderWheel,sliderThrottle);
  //buttons
  buttonStart = controlDevice.getButton(&quot;Button 7&quot;);      //START BUTTON
  buttonESC = controlDevice.getButton(&quot;Button 6&quot;);     //BACK BUTTON
  buttonSendSettings = controlDevice.getButton(&quot;Button 7&quot;);      //START BUTTON, clutch must be in.
  buttonResetOnFlatline = controlDevice.getButton(&quot;Button 6&quot;);      //BACK BUTTON (clutch must be in for this one to work).
&nbsp;
  buttonGEARUP = controlDevice.getButton(&quot;Button 5&quot;);        //RB
  buttonGEARDOWN = controlDevice.getButton(&quot;Button 4&quot;);       //LB
  buttonCLUTCH = controlDevice.getButton(&quot;Button 9&quot;);    //RJD
&nbsp;
  buttonCentreCamera = controlDevice.getButton(&quot;Button 8&quot;);    //LJD  (press to adjust camera trim).
  buttonAutoCamera = controlDevice.getButton(&quot;Button 0&quot;);    //A. autoCamera ON/OFF
  buttonCameraMoveLeft = controlDevice.getButton(&quot;Button 2&quot;);       //X
  buttonCameraMoveRight = controlDevice.getButton(&quot;Button 1&quot;);        //B
&nbsp;
  buttonCalibrateSensors = controlDevice.getButton(&quot;Button 3&quot;);        //Y  (clutch must be in. calibrates sensors. Vehicle should be stopped!!)
  buttonSendFFaccels = controlDevice.getButton(&quot;Button 3&quot;);        //Y (when clutch is NOT in).
&nbsp;
  //cooliehat
  //handled by cooliehat: steering trim (when clutch is depressed) and camera trim (when cameraCentre is depressed).
  cooliehat = controlDevice.getCoolieHat(10);
}
void mapXBOXmac()
{
  controlDevice = controll.getDevice(&quot;Controller&quot;);
  //printDeviceItems();
  controlDevice.setTolerance(0.05f);  //was 0.5f.
  wheelStickDeadZone = 0.005;    //stick neutral deadzone
  wheelHardValue = 0.93;
  wheelMovementMin = 0.002;  //wheel has to be moved this much in order to calculate new value.
  wheelPOW = 1.0;
  pedalDeadZone = 0.004;
  pedalMovementMin = 0.002;
  pedalHardValue = 0.95;
  cameraMoveInterval = 6; //milliseconds between camera steps.
  cameraMovementMin = 0.01; //if camera movement is controlled by a stick, the stick has to be moved this much to calculate a new value.
  cameraStickDeadZone = 0.2;
  cameraHardValue = 0.90;
  cameraMaxStep = 6;
  cameraMinStep = 1;
  hasCoolieHat = true;    //the controller has a cooliehat.
  coolieIsCoolie = false;  //the cooliehat is broken into buttons.
  clutchForGears = true;  //don't need a clutch to go up a gear.
  hasForRevGear = false;  //false. used to determine if we should pay attention to &quot;direction&quot;.
  oneSliderForThrottle = false; //this controller has one slider for each pedal...
  hasReversePedal = true; //this controller has a dedicated reverse pedal.
  wheelIsStick = true;    //steering is controlled by a stick.
  throttleIsStick = false; //make sure you use pedal deadzones.
  hasCameraStick = true;   //is camera control a stick on this controller?
  hasCameraButtons = true;
  hasCameraCentreButton = true;
  hasRumblers = 2;  //controller has rumblers, though I can't get them to work.
  maxRumbleIntensity = 1.0;
  throttleBase = 1;  //0 = -1 to 1, rests at 0. 1 = -1 to 1, rests at -1. 2 is SPLIT, rests at 0, goes from 0 to -1, 0 to 1.
  reverseBase = 1;
  clutchBase = 0;
  //sliders
  sliderWheel = controlDevice.getSlider(2);
  sliderThrottle = controlDevice.getSlider(5);
  sliderReverse = controlDevice.getSlider(4);
  sliderCameraX = controlDevice.getSlider(0);
  //buttons
  buttonStart = controlDevice.getButton(4);      //START BUTTON
  buttonESC = controlDevice.getButton(5);     //BACK BUTTON
  buttonSendSettings = controlDevice.getButton(4); //start button, clutch must be in.
  buttonResetOnFlatline = controlDevice.getButton(5);      //back BUTTON (clutch must be in for this one to work).
&nbsp;
  buttonGEARUP = controlDevice.getButton(9);        //RB
  buttonGEARDOWN = controlDevice.getButton(8);       //LB
&nbsp;
  buttonCLUTCH = controlDevice.getButton(7);    //RJD
&nbsp;
  buttonCentreCamera = controlDevice.getButton(6);    //LJD (also pressed to adjust camera trim).
  buttonAutoCamera = controlDevice.getButton(11);    //A. autoCamera ON/OFF
  buttonCameraMoveLeft = controlDevice.getButton(13);       //X
  buttonCameraMoveRight = controlDevice.getButton(12);        //B
&nbsp;
  buttonCalibrateSensors = controlDevice.getButton(14);        //Y  (clutch must be in. calibrates sensors. Vehicle should be stopped!!)
  buttonSendFFaccels = controlDevice.getButton(14);        //Y (when clutch is NOT in).
&nbsp;
  //cooliehat
  //handled by cooliehat: steering trim (when clutch is depressed) and camera trim (when cameraCentre is depressed).
  coolieUP = controlDevice.getButton(0);    //cooliehat UP
  coolieDOWN = controlDevice.getButton(1);    //cooliehat DOWN
  coolieLEFT = controlDevice.getButton(2);    //cooliehat LEFT
  coolieRIGHT = controlDevice.getButton(3);    //cooliehat RIGHT 
}
void mapG27windows()
{
  controlDevice = controll.getDevice(&quot;Logitech G27 Racing Wheel USB&quot;);
  printDeviceItems();
  controlDevice.setTolerance(0.0);  //was 0.5f.
  wheelMovementMin = 0.000;  //wheel has to be moved this much in order to calculate new value.
  wheelHardValue = 0.989;  //very sensitive and accurate controller... good job logitech!
  wheelDeadZone = 0.00;    //WHEEL, NOT stick, neutral deadzone. None for the G27.
  wheelPOW = 1.0;
  pedalDeadZone = 0.004;
  pedalMovementMin = 0.002;
  pedalHardValue = 0.95;
  clutchThrow = 0.52;  //percentage the clutch must be pushed to activate.
  cameraMoveInterval = 6; //milliseconds between camera steps.
  cameraMovementMin = 0.01; //NOT on this one. if camera movement is controlled by a stick, the stick has to be moved this much to calculate a new value.
  cameraStickDeadZone = 0.2;  //no
  cameraHardValue = 0.90;   //no
  cameraMaxStep = 8;   
  cameraMinStep = 2;
  hasCoolieHat = true;    //the controller has a cooliehat.
  coolieIsCoolie = true;  //the cooliehat is broken into buttons.
  clutchForGears = true;  //does the clutch need to be depressed to change gears?
  hasSlidingClutch = true;
  hasForRevGear = true;  //it does... used to determine if we should pay attention to &quot;direction&quot;.
  oneSliderForThrottle = true; //even though there are two pedals, on this controller in windows one slider represents both pedals.
  hasReversePedal = true; //I use the brake pedal as the reverse pedal... keeps things simple
  wheelIsStick = false;    //steering is controlled by a stick.
  throttleIsStick = false; //make sure you use pedal deadzones.
  hasCameraStick = false;   //is camera control a stick on this controller?
  hasCameraButtons = true;
  hasCameraCentreButton = true;
  hasRumblers = 5;  //5 rumblers...?
  maxRumbleIntensity = 0.60;
  throttleBase = 4;  //0 = -1 to 1, rests at 0. 1 = -1 to 1, rests at -1. 2= SPLIT across two padels, rests at 0, range: 1 to -1, 4 is 1 to -1 and rests at 1.
  reverseBase = 4;
  clutchBase = 4;
  //sliders
  sliderWheel = controlDevice.getSlider(0);
  sliderThrottle = controlDevice.getSlider(3);
  sliderReverse = controlDevice.getSlider(2);  //not needed
  sliderClutch = controlDevice.getSlider(4);
  //sliderCameraX = controlDevice.getSlider(1);
  //buttons
  buttonStart = controlDevice.getButton(1);      //STICKSHIFT REDBUTTON 1
  buttonESC = controlDevice.getButton(2);     //STICKSHIFT REDBUTTON 2
  buttonSendSettings = controlDevice.getButton(1);      //STICKSHIFT REDBUTTON 1, clutch must be in.
  buttonResetOnFlatline = controlDevice.getButton(2);      //STICKSHIFT REDBUTTON 2 (clutch must be in for this one to work).
&nbsp;
  buttonGEARDOWN = controlDevice.getButton(6);       //Left Paddle
  buttonGEARUP = controlDevice.getButton(5);        //Right Paddle
  //buttonCLUTCH = controlDevice.getButton(&quot;Button 9&quot;);    //NOT a button clutch for this controller.
&nbsp;
  buttonCentreCamera = controlDevice.getButton(16);    //POV UP/FORWARD/TOP. Need to push this while adjusting camera trim.  
  buttonAutoCamera = controlDevice.getButton(18);    //POV DOWN. autoCamera ON/OFF
  buttonCameraMoveLeft = controlDevice.getButton(19);       //POV LEFT.
  buttonCameraMoveRight = controlDevice.getButton(17);        //POV RIGHT.
&nbsp;
  buttonCalibrateSensors = controlDevice.getButton(3);        //STICKSHIFT REDBUTTON 3  (clutch must be in. calibrates sensors. Vehicle should be stopped!!)
  buttonSendFFaccels = controlDevice.getButton(3);        //STICKSHIFT REDBUTTON 3 (when clutch is NOT in).
&nbsp;
  //cooliehat
  //handled by cooliehat: steering trim (when clutch is depressed) and camera trim (when cameraCentre is depressed).
  cooliehat = controlDevice.getCoolieHat(0);
&nbsp;
  //THE G27 has a gear shifter!!
  //currently using it as direction button (1,3,5 = forward, 2,4,6= reverse)
  buttonGEAR1 = controlDevice.getButton(9);
  buttonGEAR2 = controlDevice.getButton(10);
  buttonGEAR3 = controlDevice.getButton(11);
  buttonGEAR4 = controlDevice.getButton(12);
  buttonGEAR5 = controlDevice.getButton(13);
  buttonGEAR6 = controlDevice.getButton(14);
  buttonGEARreverse = controlDevice.getButton(15);
&nbsp;
  //spur the device with a rumble command... (activates G27 for me).
  controlDevice.rumble(0.3, 1);
  delay(333);
  controlDevice.rumble(0.0, 1);
}
void mapF710xInputWindows()
{
  controlDevice = controll.getDevice(&quot;Controller (Rumble Gamepad F510)&quot;);
  //printDeviceItems();
  controlDevice.setTolerance(0.05f);  //was 0.5f.
  wheelMovementMin = 0.001;  //wheel has to be moved this much in order to calculate new value.
  wheelStickDeadZone = 0.005;    //stick neutral deadzone
  wheelHardValue = 0.95;
  wheelPOW = 1.0;
  pedalDeadZone = 0.004;
  pedalMovementMin = 0.002;
  pedalHardValue = 0.95;
  cameraMoveInterval = 6; //milliseconds between camera steps.
  cameraMovementMin = 0.01; //if camera movement is controlled by a stick, the stick has to be moved this much to calculate a new value.
  cameraStickDeadZone = 0.2;
  cameraHardValue = 0.90;
  cameraMaxStep = 6;
  cameraMinStep = 1;
  hasCoolieHat = true;    //the controller has a cooliehat.
  coolieIsCoolie = true;  //the cooliehat is broken into buttons.
  clutchForGears = true;  //does the clutch need to be depressed to change gears?
  hasSlidingClutch = false;
  hasForRevGear = false;  //false. used to determine if we should pay attention to &quot;direction&quot;.
  oneSliderForThrottle = true; //even though there are two pedals, on this controller in windows one slider represents both pedals.
  hasReversePedal = false; //this controller does not have a dedicated reverse pedal.
  wheelIsStick = true;    //steering is controlled by a stick.
  throttleIsStick = false; //make sure you use pedal deadzones.
  hasCameraStick = true;   //is camera control a stick on this controller?
  hasCameraButtons = true;
  hasCameraCentreButton = true;
  hasRumblers = 2;  //controller has rumblers
  maxRumbleIntensity = 1.0;
  throttleBase = 2;  //0 = -1 to 1, rests at 0. 1 = -1 to 1, rests at -1. 2= SPLIT across two padels, rests at 0, range: 1 to -1.
  reverseBase = 0;
  clutchBase = 0;
  //sliders
  sliderWheel = controlDevice.getSlider(3);
  sliderThrottle = controlDevice.getSlider(4);
  sliderCameraX = controlDevice.getSlider(1);
  //stick = new ControllStick(sliderWheel,sliderThrottle);
  //buttons
  buttonStart = controlDevice.getButton(7);      //START BUTTON
  buttonESC = controlDevice.getButton(6);     //BACK BUTTON
  buttonSendSettings = controlDevice.getButton(7);      //START BUTTON, clutch must be in.
  buttonResetOnFlatline = controlDevice.getButton(6);      //BACK BUTTON (clutch must be in for this one to work).
&nbsp;
  buttonGEARUP = controlDevice.getButton(5);        //RB
  buttonGEARDOWN = controlDevice.getButton(4);       //LB
  buttonCLUTCH = controlDevice.getButton(9);    //RJD
&nbsp;
  buttonCentreCamera = controlDevice.getButton(8);    //LJD  (press to adjust camera trim).
  buttonAutoCamera = controlDevice.getButton(0);    //A. autoCamera ON/OFF
  buttonCameraMoveLeft = controlDevice.getButton(2);       //X
  buttonCameraMoveRight = controlDevice.getButton(1);        //B
&nbsp;
  buttonCalibrateSensors = controlDevice.getButton(3);        //Y  (clutch must be in. calibrates sensors. Vehicle should be stopped!!)
  buttonSendFFaccels = controlDevice.getButton(3);        //Y (when clutch is NOT in).
&nbsp;
  //cooliehat
  //handled by cooliehat: steering trim (when clutch is depressed) and camera trim (when cameraCentre is depressed).
  cooliehat = controlDevice.getCoolieHat(10);
}
void mapF710DirectInputWindows()
{
  controlDevice = controll.getDevice(&quot;Logitech Rumblepad 2 USB&quot;);
  //printDeviceItems();
  controlDevice.setTolerance(0.05f);  //was 0.5f.
  wheelMovementMin = 0.001;  //wheel has to be moved this much in order to calculate new value.
  wheelStickDeadZone = 0.005;    //stick neutral deadzone
  wheelHardValue = 0.95;
  wheelPOW = 1.0;
  throttleStickDeadzone = 0.02; //throttle neutral deadzone.
  pedalDeadZone = 0.004;
  pedalMovementMin = 0.002;
  pedalHardValue = 0.95;
  cameraMoveInterval = 6; //milliseconds between camera steps.
  cameraMovementMin = 0.01; //if camera movement is controlled by a stick, the stick has to be moved this much to calculate a new value.
  cameraStickDeadZone = 0.2;
  cameraHardValue = 0.90;
  cameraMaxStep = 5;
  cameraMinStep = 1;
  hasCoolieHat = true;    //the controller has a cooliehat.
  coolieIsCoolie = true;  //the cooliehat is broken into buttons.
  clutchForGears = true;  //does the clutch need to be depressed to change gears?
  hasSlidingClutch = false;
  hasForRevGear = false;  //false. used to determine if we should pay attention to &quot;direction&quot;.
  oneSliderForThrottle = true; //it's a stick on this one.
  hasReversePedal = false; //this controller does not have a dedicated reverse pedal.
  wheelIsStick = true;    //steering is controlled by a stick.
  throttleIsStick = true; //make sure you use stick deadzones
  hasCameraStick = false;   //is camera control a stick on this controller?
  hasCameraButtons = true;
  hasCameraCentreButton = true;
  hasRumblers = 2;  //controller has rumblers
  maxRumbleIntensity = 5.0;
  throttleBase = 2;  //0 = -1 to 1, rests at 0. 1 = -1 to 1, rests at -1. 2= SPLIT across two padels, rests at 0, range: 1 to -1.
  reverseBase = 0;
  clutchBase = 0;
  //sliders
  sliderWheel = controlDevice.getSlider(1);
  sliderThrottle = controlDevice.getSlider(2);
  //sliderCameraX = controlDevice.getSlider(3);
  //stick = new ControllStick(sliderWheel,sliderThrottle);
  //buttons
  buttonStart = controlDevice.getButton(10);      //START BUTTON
  buttonESC = controlDevice.getButton(9);     //BACK BUTTON
  buttonSendSettings = controlDevice.getButton(10);      //START BUTTON, clutch must be in.
  buttonResetOnFlatline = controlDevice.getButton(9);      //BACK BUTTON (clutch must be in for this one to work).
&nbsp;
  buttonGEARUP = controlDevice.getButton(6);        //RB
  buttonGEARDOWN = controlDevice.getButton(5);       //LB
  buttonCLUTCH = controlDevice.getButton(12);    //RJD
&nbsp;
  buttonCentreCamera = controlDevice.getButton(11);    //LJD  (press to adjust camera trim).
  buttonAutoCamera = controlDevice.getButton(2);    //A. autoCamera ON/OFF
  buttonCameraMoveLeft = controlDevice.getButton(1);       //X
  buttonCameraMoveRight = controlDevice.getButton(3);        //B
&nbsp;
  buttonCalibrateSensors = controlDevice.getButton(4);        //Y  (clutch must be in. calibrates sensors. Vehicle should be stopped!!)
  buttonSendFFaccels = controlDevice.getButton(4);        //Y (when clutch is NOT in).
&nbsp;
  //cooliehat
  //handled by cooliehat: steering trim (when clutch is depressed) and camera trim (when cameraCentre is depressed).
  cooliehat = controlDevice.getCoolieHat(0);
}
void printDeviceItems()
{
  controlDevice.printSticks();
  controlDevice.printSliders();
  controlDevice.printButtons(); 
}
void setController()
{
  controll = ControllIO.getInstance(this);
  println(&quot;Controller: &quot; + usingController);
  if(usingController == 0) {
    //we're using the ps3 controller on the Mac OS X
    mapPS3mac();
  } else if (usingController == 1) {
    //using XBOX360 on Windows 7
    mapXBOXwindows();
  } else if (usingController == 2) {
    //using XBOX360 on Mac OS X
    mapXBOXmac();
  } else if (usingController == 3) {
    //using XBOX360 on Windows 7
    mapG27windows();
  } else if (usingController == 4) {
    //using Logitech F510 in xInput mode on Windows
    mapF710xInputWindows();
  } else if (usingController == 5) {
    //using Logitech F510 in DirectInput mode on Windows
    mapF710DirectInputWindows();
  }
}
&nbsp;
&nbsp;
////////////////////////////////////////////
//                                        //
//         Data Handling Functions        //
//                                        //
////////////////////////////////////////////
String ints(int theInt)
{
  //a shorthand function for turning strings into integers.
  return Integer.toString(theInt);
}
String intf(float theFloat) {
  //a shorthand function for turning strings into floats.
  return Float.toString(theFloat);
}
float convertSliderBase(float theReading, int theSliderBase)
{
  //this converts the slider to a 0 to 1 base
  float theConvertedReading = 0.0;
  if(theSliderBase == 0) {
    //it's already 0 to 1, do nothing.
    theConvertedReading = theReading;
  } else if (theSliderBase == 1) {
    //the slider base is -1 to 1.
    theConvertedReading = (theReading + 1.0) / 2.0;
  } else if (theSliderBase == 2) {
    //SPLIT. rests at 0, and travels from 1 to -1.
    //xbox 360 on windows has this.
    theConvertedReading = theReading * -1; //flip the reading
  } else if (theSliderBase == 3) {
    //on logitech gamepad, pedals are combined. Right pedal produces -1, left +1.
    //so, flip that:
    theReading = theReading * -1.0;
    //now treat like sliderbase 1:
    theConvertedReading = (theReading + 1.0) / 2.0;
  } else if (theSliderBase == 4) {
    //the slider base is 1 to -1, rests at 1.
    theConvertedReading = ((theReading - 1.0) * -1.0) / 2.0;
  } 
  //delay(666);
  //println(&quot;theReading: &quot; + theReading + &quot;, theConvertedReading = &quot; + theConvertedReading);
  return(theConvertedReading);
}
float accountForSliderZones(float theCurrentReading, float theDeadZone, float theHardVal)
{
  float hardNegative = theHardVal * -1.0;
  float theDeadZoneNegative = theDeadZone * -1.0;
  float readingAdjusted;
  float theDenominator = theHardVal - theDeadZone;
  if(theCurrentReading &lt; theDeadZoneNegative) {
      //stick is moved to the left
      if(theCurrentReading &gt; hardNegative) {
        float posReading = theCurrentReading * -1.0;
        readingAdjusted = ((posReading - theDeadZone) / theDenominator) * -1.0;
      } else {
        readingAdjusted = -1.0;
      }
  } else if(theCurrentReading &gt; theDeadZone) {
      //stick is moved to the right.
      if(theCurrentReading &lt; theHardVal) {
        readingAdjusted = (theCurrentReading - theDeadZone) / theDenominator;
      } else {
        readingAdjusted = 1.0;
      }
  } else {
    //in the deadzone
    readingAdjusted = 0.0;
  }
  return(readingAdjusted);
}
&nbsp;
&nbsp;
////////////////////////////////////////////
//                                        //
//          FORCE FEEDBACK                //
//          Handling/Execution            //
//                                        //
////////////////////////////////////////////
void handleRumbling() {
  //you might notice this is geared to the G27, since it's the only device I can get to rumble through processing...so far.
  if(rumbling) {
    //first, are any of the rumblers activated?
    theTime = millis();
    if(theTime &gt; timeFFreduce) {
      //has the time come to reduce the force feedback intensity if it hasn't already been zeroed?
      //make sure the values are not less than zero.
      float rumbleMin = 0.04;
      float rumbleReduce = 0.077;
      if((rumble0val &lt;= rumbleMin) &amp;&amp; rumbling0) {
        rumble0val = 0.0;
        controlDevice.rumble(rumble0val, 0);
        rumbling0 = false;
        //println(&quot;feedback Z zeroed&quot;);
      } else if ((rumble1val &lt;= rumbleMin) &amp;&amp; rumbling1) {
        rumble1val = 0.0;
        controlDevice.rumble(rumble1val, 1);
        rumbling1 = false;
        //println(&quot;feedback Y zeroed&quot;);
      } else if ((rumble2val &lt;= rumbleMin) &amp;&amp; rumbling2) {
        rumble2val = 0.0;
        controlDevice.rumble(rumble2val, 2);
        rumbling2 = false;
        //println(&quot;feedback X zeroed&quot;);
      }
      //handle rumbler0
      if(rumble0val &gt; rumbleMin) {
        controlDevice.rumble(rumble0val, 0);
        rumbling0 = true;
        rumble0val = rumble0val - rumbleReduce;  //reduce the value slightly for the next run.
&nbsp;
      }
      //handle rumbler1
      if(rumble1val &gt; rumbleMin) {
        controlDevice.rumble(rumble1val, 1);
        rumbling1 = true;
        rumble1val = rumble1val - rumbleReduce;  //reduce the value slightly for the next run.
      }
      //handle rumbler2
      if(rumble2val &gt; rumbleMin) {
        controlDevice.rumble(rumble2val, 2);
        rumbling2 = true;
        rumble2val = rumble2val - rumbleReduce;  //reduce the value slightly for the next run.
      }
      timeFFreduce = theTime + timeFFdelay;
    }
  } else if (!rumbling) {
    //we've likely received the &quot;no more rumbling&quot; indicator.
    if(rumbling0) {
      controlDevice.rumble(0.0, 0);
    }
    if(rumbling1) {
      controlDevice.rumble(0.0, 1);
    }
    if(rumbling2) {
      controlDevice.rumble(0.0, 2);
    }
  }
&nbsp;
  if(!rumbling0 &amp;&amp; !rumbling1 &amp;&amp; !rumbling2) {
    rumbling = false;
  }
}
void doRumble(int rumbleID, int rumbleValue) {
  //this function will probably become fairly complex...
  //VERY simple at the moment.
  theTime = millis();
  feedbackreceived = rumbleValue;   //int to float.
&nbsp;
  println(&quot;rumbleID: &quot; + rumbleID + &quot;,  rumbleValue: &quot; + rumbleValue);
&nbsp;
  feedbacksetting = ((float)rumbleValue * 1000.0) / ((float)aDivider * 1000.0);
  if (feedbacksetting &gt; maxRumbleIntensity) {
    feedbacksetting = maxRumbleIntensity;
  }
  //println(&quot;feedbacksetting = &quot; + feedbacksetting);
&nbsp;
  rumbling = true;  //set rumbling true, tentatively...
  if(rumbleID == 2) {
    rumble2val = feedbacksetting;
    //println(&quot;it's X&quot;);
  } else if (rumbleID == 1) {
    rumble1val = feedbacksetting;
    //println(&quot;it's Y&quot;);
  } else if (rumbleID == 0) {
    rumble0val = feedbacksetting;
    //println(&quot;it's Z: &quot; + feedbacksetting);
  } else if (rumbleID == 77) {
    //println(&quot;CANCEL ALL RUMBLING&quot;);
    //rumbling = false;
  }
}
&nbsp;
////////////////////////////////////////////
//                                        //
//          Vehicle Commands              //
//                                        //
////////////////////////////////////////////
////////////////////////////////////////////
//                                        //
//         Communication Functions        //
//                                        //
////////////////////////////////////////////
void sendCom()
{
  if(comDelayRequired) {
    //for commands that would otherwise get discarded  
    comMsg = delayedCommands + comMsg;
    println(comMsg);
    delayedCommands = &quot;&quot;;
    comDelayRequired = false;
  }
&nbsp;
  //build comMsg
  if(!autocameraOn) {
    comMsg = CsetCamera + cameraSetting + comMsg; //add camera
  }
  comMsg = CsetWheel + wheelSetting + comMsg; //add wheel
  comMsg = CsetThrottle + throttleSetting + comMsg; //add throttle
&nbsp;
  if(comMsg != &quot;&quot;) {
    udp.send(comMsg, ip, sendPort );  //send command
    //println(comMsg);
    commandCounter++;
    delay(repeatDelay);
    comMsg = &quot;&quot;;
  }
}
void setSteeringRange(int theSetting)
{
  instruction = CsetSteeringRange + ints(theSetting);
  comMsg = comMsg + instruction;
}
void setWheelCentre(int theSetting)
{
 instruction = CsetWheelCentre + ints(theSetting);
 comMsg = comMsg + instruction;
}
//void sendSensorThresholds()
//{
  //intf used for float to string conversion
  //instruction = CaAmin + ints(minAccelFFamplitudeZ) + endOfCommand;
  //sendCom(instruction);  //set minimum accelerometer force feedback amplitude.
  //delay(27);
  //instruction = CaDivider + ints(aDivider) + endOfCommand;
  //sendCom(instruction);  //set minimum accelerometer force feedback amplitude.
  //delay(27);
  //println(&quot;Sensor Thresholds Sent&quot;); 
//}
void sendSettings()
{
  println(&quot;Sending Settings...&quot;); 
  //this function is meant to send every possible setting that the arduino needs to remember...
  sCS = 0; //settings checksum, reset to zero.
  instruction = CSCS + ints(sCS);
  comMsg = comMsg + instruction; //tell arduino wifly mini to reset its settings checksum to zero.
  //set wheelCentre
  setWheelCentre(wheelCentre);  //sets the wheel centre... the function adds the instruction to comMsg itself
  sCS++; //add one to the checksum every time we send a setting.
  //set the steering range
  setSteeringRange(steeringRange);  //sets steering range, function adds instruction to comMsg.
  sCS++; //add one to the checksum every time we send a setting.
&nbsp;
&nbsp;
  //set the camera centre 
  instruction = CsetCameraCentre + ints(cameraCentre);
  comMsg = comMsg + instruction;
  sCS++; //add one to the checksum every time we send a setting.
  //set autocamera range.
  instruction = CsetAutoCameraRange + ints(autoCameraRange);
  comMsg = comMsg + instruction;
  sCS++; //add one to the checksum every time we send a setting.
&nbsp;
&nbsp;
  //set minimum accelerometer force feedback amplitudes, X, Y, then Z.
  instruction = CaAminX + ints(minAccelFFamplitudeX);
  comMsg = comMsg + instruction;
  sCS++; //add one to the checksum every time we send a setting.
&nbsp;
  instruction = CaAminY + ints(minAccelFFamplitudeY);
  comMsg = comMsg + instruction;
  sCS++; //add one to the checksum every time we send a setting.
&nbsp;
  instruction = CaAminZ + ints(minAccelFFamplitudeZ);
  comMsg = comMsg + instruction;
  sCS++; //add one to the checksum every time we send a setting.
&nbsp;
  //ask the WIFLY for a settings comparison.
  println(&quot;Ask WIFLY for confirmation checksum: &quot; + sCS);
  instruction = CSCS + ints(sCS);
  comMsg = comMsg + instruction;
}
void calibrateSensors()
{
  if(settingsSent) {
    println(&quot;Calibrate Sensors&quot;);
    sendCom();
    comMsg = comMsg + CcalibrateSensors;
  } else {
    println(&quot;Can't calibrate sensors: settings haven't been sent.&quot;);
  }
}
void ffSendAccels()
{
  //println(&quot;Switch On/Off ACCELEROMETER ForceFeedback&quot;);
  comMsg = comMsg + CsendFFaccels;
}
void neutralAndCentre()
{
      wheelSetting = wheelCentre;
      setSteeringRange(steeringRange);
      setWheelCentre(wheelCentre);  //sets the default wheel centre on arduino.
      throttleSetting = throttleNeutral;
}
void startVehicle()
{
  println(&quot;Sent S.&quot;);
  comMsg = comMsg + Cstart;
}
void escPOWER()
{
  if(started == true){
    println(&quot;Sent E.&quot;);
    neutralAndCentre();
    comMsg = comMsg + CswitchESC;  //tell the ESC to power up.
  } else {
    println(&quot;Not Started&quot;);
  }
}
void resetOnFlatline()
{
    comMsg = comMsg + CresetOnFlatline + CresetOnFlatline + CresetOnFlatline;    
}
void centreCamera()
{
  theTime = millis();
  cameraSetting = cameraCentre;
  cameraSetting = cameraCentre;
  cameraStep = 0;
  cameraMoveTime = theTime + 555;
  println(&quot;Camera Centered&quot;);
}
void autoCamera()
{
  comMsg = comMsg + CautoCamera;
}
void moveCamera(int theStep) 
{
  if((theStep &lt; 0) || (theStep &gt; 0)) {
      theTime = millis();
      if(cameraMoveTime &lt; theTime) {
        if(cameraMaxLeft &gt; cameraMaxRight) {
          //camera max left greater than max right.
          if((cameraSetting &gt;= cameraMaxRight) &amp;&amp; (cameraSetting &lt;= cameraMaxLeft)) {
            cameraSetting = cameraSetting + theStep;
          }
          if(cameraSetting &lt; cameraMaxRight) {
            //camera setting has travelled beyond camera limit
            cameraSetting = int(cameraMaxRight);  
          } else if (cameraSetting &gt; cameraMaxLeft) {
            cameraSetting = int(cameraMaxLeft);
          }
        } else if (cameraMaxLeft &lt; cameraMaxRight) {
          //camera max left less than max right.
          if((cameraSetting &gt;= cameraMaxLeft) &amp;&amp; (cameraSetting &lt;= cameraMaxRight)) {
            cameraSetting = cameraSetting + theStep;
          }
          if(cameraSetting &lt; cameraMaxLeft) {
            //camera setting has travelled beyond camera limit
            cameraSetting = int(cameraMaxLeft);  
          } else if (cameraSetting &gt; cameraMaxRight) {
            cameraSetting = int(cameraMaxRight);
          }
        }
        //println(&quot;theStep: &quot; + theStep + &quot;, cameraSetting: &quot; + cameraSetting);
        //setCamera(cameraSetting);
        cameraMoveTime = theTime + cameraMoveInterval;
      }
  }  
}
void adjustSteeringTrim(int trimAdjustAmt)
{
  if(clutchIn) {
    wheelCentre = wheelCentre + trimAdjustAmt;
    wheelSetting  = wheelSetting + trimAdjustAmt;
    println(&quot;wheelCentre: &quot; + wheelCentre);
  }
}
void adjustCameraTrim(int trimAdjustAmt)
{
  if(buttonCentreCameraPressed) {
    cameraCentre = cameraCentre + trimAdjustAmt;
    cameraSetting  = cameraSetting + trimAdjustAmt;
    println(&quot;cameraCentre: &quot; + cameraCentre);
  }
}
void updateheart()
{
  theTime = millis();
  beatTimer = theTime + heartRate;
}
void heartbeat()
{
  if (started == true) { 
    theTime = millis();
    if ((theTime &gt;= beatTimer) || (commandCounter &gt; commandsSentToHeartbeat)) {
      //if the beattimer has expired, or the number of commands sent has exceeded the commandcounter, a heartbeat is sent.
      //println(beatTimer);
      comMsg = comMsg + CheartBeat;
      updateheart();
      commandCounter = 0;
    }
  }
}
void delegate() {
      if (aCommand.equals(&quot;A&quot;)) {
        println(&quot;WIFLY: ESC is ON.&quot;);
        escOn = true;
      } else if (aCommand.equals(&quot;a&quot;)) {
        println(&quot;WIFLY: ESC is OFF.&quot;);
        escOn = false;
      } else if (aCommand.equals(&quot;S&quot;)) {
        println(&quot;WIFLY: STARTED&quot;);
        started = true;
      } else if (aCommand.equals(&quot;s&quot;)) {
        println(&quot;WIFLY: STOPPED&quot;);
        started = false;
      } else if (aCommand.equals(&quot;X&quot;)) {
        //wifly telling us accel send switch is on or off
        if(aCommandVal == 0) {
          println(&quot;WIFLY: Stop Accel FF&quot;);  
        } else if (aCommandVal == 1) {
          println(&quot;WIFLY: Start Accel FF&quot;);
        }
      } else if (aCommand.equals(&quot;h&quot;)) {
        //wifly telling us accel send switch is on or off
        if(aCommandVal == 0) {
          println(&quot;WIFLY: AutoCamera OFF&quot;);
          autocameraOn = false;
        } else if (aCommandVal == 1) {
          println(&quot;WIFLY: AutoCamera ON&quot;);
          autocameraOn = true;
        }
      } else if (aCommand.equals(&quot;p&quot;)) {
        //arduino says it has not yet received settings. please send them.
        aCommand = &quot;p&quot;;
        settingsSent = false;
        println(&quot;WIFLY: Please Send Settings!&quot;);
      } else if (aCommand.equals(&quot;M&quot;)) {
        //receiving settings checksum.
        if(sCS == aCommandVal) {
          println(&quot;Settings Checksum Good, Sending Confirmation...&quot;);
          //send confirmation of good checksum.
          settingsSent = true;
          comDelayRequired = true;
          delayedCommands = CsCSgood + delayedCommands;
        } else {
          println(&quot;Settings Checksum BAD (&quot; + aCommandVal + &quot;). Try again.&quot;);
          //do nothing.
        }
      } else if (aCommand.equals(&quot;Z&quot;)) {
        //wifly telling us the WIFLY RESET ON FLATLINE switch is on or off.
        if(aCommandVal == 0) {
          println(&quot;WIFLY: Will NOT Reset on Flatline&quot;);  
        } else if (aCommandVal == 1) {
          println(&quot;WIFLY: Will Reset on Flatline&quot;);
        }
      } else if (aCommand.equals(&quot;G&quot;)) {
        println(&quot;WIFLY: Finished Sensor Calibration&quot;);
      } else {
        //do nothing
      }
&nbsp;
  if(hasRumblers &gt; 0) {
    if(aCommand.equals(&quot;&lt;&quot;)) {
      doRumble(2, aCommandVal);
    } else if (aCommand.equals(&quot;&gt;&quot;)) {
      doRumble(1, aCommandVal);
    } else if (aCommand.equals(&quot;^&quot;)) {
      doRumble(0, aCommandVal);
    } else if (aCommand.equals(&quot;&lt;&quot;)) {
      //no more rumbling!
      doRumble(77, aCommandVal);
    }
  }
&nbsp;
  //println(&quot;Delegate Says: aCommand = &quot; + aCommand + &quot;, aCommandVal = &quot; + aCommandVal);
}
void receive( byte[] data ) {
  //void receive( byte[] data, String ip, int port ) {   // &lt;-- extended handler
  char[] mrTdata = new char[data.length]; //FOR CONVERTING BYTE TO CHAR. here is stored information coming from the arduino.
  String incomingDataString = &quot;&quot;; //makes a whole string out of data presently being received...
  String firstChar = &quot;&quot;;
  String currentChar = &quot;&quot;;
&nbsp;
&nbsp;
  for (int i=0; i &lt; data.length; i++) {
    mrTdata[i] = char(data[i]);   //BYTE TO CHAR.
    incomingDataString = incomingDataString + mrTdata[i];  //added to whole string used for reference.
    firstChar = &quot;&quot; + mrTdata[0];   //store the first char for reference... &quot;&quot; appears to be necessary to force conversion from CHAR to STRING
    currentChar = &quot;&quot; + mrTdata[i];
&nbsp;
    if(!currentChar.equals(&quot;1&quot;) &amp;&amp; !currentChar.equals(&quot;2&quot;) &amp;&amp; !currentChar.equals(&quot;3&quot;) &amp;&amp; !currentChar.equals(&quot;4&quot;) &amp;&amp; !currentChar.equals(&quot;5&quot;) &amp;&amp; !currentChar.equals(&quot;6&quot;) &amp;&amp; !currentChar.equals(&quot;7&quot;) &amp;&amp; !currentChar.equals(&quot;8&quot;) &amp;&amp; !currentChar.equals(&quot;9&quot;) &amp;&amp; !currentChar.equals(&quot;0&quot;) &amp;&amp; !currentChar.equals(&quot;.&quot;)) { 
      //the character is not a number, not a value to go along with a command,
      //so it is probably a command.
      if(instructionDataString != &quot;&quot;) {
        aCommandVal = Integer.parseInt(instructionDataString);
      }
      if(aCommand != &quot;&quot;) {
        delegate();
      }
      aCommand = currentChar;
      instructionDataString = &quot;&quot;;
    } else {
      //in this case, we're probably receiving a command value.
      //store it
      instructionDataString = instructionDataString + currentChar;
    }
&nbsp;
  }
}
&nbsp;
////////////////////////////////////////////
//                                        //
//            SLIDER Handling             //
//                                        //
////////////////////////////////////////////
void shThrottle()
{
  //handles brakes, reverse, and forward throttle.
  //Note: convertSliderBase's function is to convert outputs from sliders to a negative one to positive one scale
  //NOTE: accountForSliderZones should be used after convertSliderBase
  //float readingAdjusted = accountForSliderZones(throttleCurrentReading, pedalDeadZone, pedalHardValue);
&nbsp;
  //reset currentReadingValues
  throttleCurrentReading = 0.0;
  reverseCurrentReading = 0.0;
  float throttleReadingAccounted = 0.0;
  float reverseReadingAccounted = 0.0;
&nbsp;
  //READ SLIDERS AND SORT CASES
&nbsp;
    if (hasReversePedal) {
      //this case exists for controllers like the xbox 360 on mac, which has two pedals...
      //note: the Xbox 360 controller on Windows are not accounted for in these if statements, &quot;hasReversePedal&quot;, since it's technically ONE slider.
      reverseCurrentReading = convertSliderBase(sliderReverse.getValue(), reverseBase);
      reverseReadingAccounted = accountForSliderZones(reverseCurrentReading, pedalDeadZone, pedalHardValue);  //should always be a pedal.
      if(reverseReadingAccounted &lt; 0.0) {
        reverseReadingAccounted = reverseReadingAccounted * -1.0;  //make sure the reading is positive. (functions below pay attention to CASES reversing, braking, etc.
      }
      //now determine if we are reversing.
      if(reverseReadingAccounted &gt; 0.0) {
        //since reverseReadingAccounted is zeroed at the beginning of this function,
        //we can go ahead and put the car into reverse if reverseReadingAccounted is above zero...
        reversing = true;
      } else {
        reversing = false;
      }
    }
&nbsp;
  //!reversing should be negative in order to enter this function.
  //all controllers will (should) have a throttleSlider
  throttleCurrentReading = convertSliderBase(sliderThrottle.getValue(), throttleBase);
&nbsp;
  //account for throttle slider zones
  if(throttleIsStick) {
    //use stick deadzones.
    throttleReadingAccounted = accountForSliderZones(throttleCurrentReading, throttleStickDeadzone, pedalHardValue);
  } else {
    //use pedal deadzones.
    throttleReadingAccounted = accountForSliderZones(throttleCurrentReading, pedalDeadZone, pedalHardValue);
  }
&nbsp;
  boolean testingThrottleReadings = false; //set to true if you want to watch the readings up to this point.
  if(testingThrottleReadings) {
    delay(500);
    println(&quot;reverseCurrentReading: &quot; + reverseCurrentReading + &quot;, reverseReadingAccounted = &quot; + reverseReadingAccounted);
    println(&quot;throttleCurrentReading: &quot; + throttleCurrentReading + &quot;, throttleReadingAccounted = &quot; + throttleReadingAccounted);
    println(&quot;====================================================&quot;);
  } else if(!testingThrottleReadings) {
  //to be deleted later. just for testing my current controllers.
&nbsp;
&nbsp;
  //now, determine what kind of throttle and amplitude should be applied as throttle.
  if (reversing) {
    //we're reversing!
    //set reverse throttle.
    if(hasForRevGear &amp;&amp; (direction == 2)) {
      //the user has indicated the main throttle slider should cause the vehicle to move in a reverse direction
      //this means that the reverse pedal should now cause the car to go forward.
      throttleSettingFloat =  throttleNeutral + (reverseReadingAccounted * throttleMax);
    } else {
      throttleSettingFloat =  throttleNeutral - (reverseReadingAccounted * throttleMaxReverse);
    }
    throttleSetting = int(throttleSettingFloat);
  } else if ( throttleReadingAccounted != 0.0 &amp;&amp; (!hasForRevGear || (hasForRevGear &amp;&amp; (direction!=0))) ) {
&nbsp;
&nbsp;
    if (hasForRevGear) {
      //this controller has a forward/reverse gear indicator...
      if(direction == 2) {
        //user has indicated the vehicle should move in reverse when throttle is pressed.
        if(throttleReadingAccounted &gt; 0.0) {
          //user is pressing on the gas, so they want to go backward
          reversing = true;
          reverseReadingAccounted = throttleReadingAccounted;
          if(throttleReadingAccounted &gt; 0.0) {
            throttleReadingAccounted = throttleReadingAccounted * -1.0;  
          }
        }
      } else if(direction == 1) {
        //user has indicated they want to go forward
        reversing = false;
      }  
    }
&nbsp;
&nbsp;
    if(throttleReadingAccounted &gt; 0) {
        //should be going forward
        throttleSettingFloat =  throttleNeutral + (throttleReadingAccounted * throttleMaxForward);
    } else if (throttleReadingAccounted &lt; 0) {
        //should be going in reverse... all preceding if statements should account for controller type
        //enough that there should be no mixups... fingers crossed.
        throttleSettingFloat =  throttleNeutral + (throttleReadingAccounted * throttleMaxReverse);
    }
    throttleSetting = int(throttleSettingFloat);
  } else {
       //account for neutral possibility.
       throttleSetting = throttleNeutral;
  } //end of braking if and then elses...
  } //end of testing throttle readings else if statement.
&nbsp;
&nbsp;
  //NOW FOR THE ACTUAL SETTING OF THE THROTTLE
  theTime = millis();
  if(throttleSetting != lastThrottleSetting) {
    //belongs in reverse too, I think...
    //setThrottle(throttleSetting);
    lastThrottleSetting = throttleSetting;
    //println(throttleSetting);
  }
&nbsp;
}
void shWheel()
{
  wheelCurrentReading = sliderWheel.getValue();
  float limitLower = wheelLastReading - wheelMovementMin;
  float limitUpper = wheelLastReading + wheelMovementMin;
  float readingAdjusted;
  float rBC; //reading bell-curved.
&nbsp;
  if((wheelCurrentReading &lt; limitLower) || (wheelCurrentReading &gt; limitUpper)) {
    //the slider has moved enough to justify new calculation...
    if(wheelIsStick) {
      //if the wheel is a stick, adjust like so
      readingAdjusted = accountForSliderZones(wheelCurrentReading,wheelStickDeadZone,wheelHardValue);
      rBC = pow(readingAdjusted, wheelPOW); //curve the reading...
    } else {
      //if the wheel is a wheel, adjust thusly...
      readingAdjusted = accountForSliderZones(wheelCurrentReading,wheelDeadZone,wheelHardValue);
      rBC = readingAdjusted;
    }
&nbsp;
    wheelSettingFloat = wheelCentre + (rBC * wheelMaxTravel);
    wheelSetting = (int)wheelSettingFloat;
&nbsp;
    //println(&quot;wheelCurrentReading = &quot; + wheelCurrentReading);
    //delay(500);
&nbsp;
&nbsp;
  }
&nbsp;
&nbsp;
    if(wheelSetting == 0) {
      println(&quot;Error: Wheel Setting 0&quot;); 
    }
    theTime = millis();
    if(wheelSetting != lastWheelSetting) {
      //did the move affect a change in wheel setting? if so, send new value.
      //setWheel(wheelSetting);
      lastWheelSetting = wheelSetting;
      //println(&quot;readingAdjusted = &quot; + readingAdjusted + &quot;, rBC = &quot; + rBC + &quot;, instruction = &quot; + instruction);
      wheelLastReading = wheelCurrentReading;
    }
}
void shClutch()
{
  clutchCurrentReading = sliderClutch.getValue();
  float translatedReading = convertSliderBase(clutchCurrentReading, clutchBase);
  if((translatedReading &gt; clutchThrow) &amp;&amp; !clutchIn) {
    //clutch is depressed
    clutchIn = true;
  } else if ((translatedReading &lt;= clutchThrow) &amp;&amp; clutchIn) {
    clutchIn = false;
  }
}
void shCamera()
{
  //camera is controlled by slider (stick, something of that nature).
  cameraCurrentReading = sliderCameraX.getValue();
  float limitLower = cameraLastReading - cameraMovementMin;
  float limitUpper = cameraLastReading + cameraMovementMin;
  float readingAdjusted;
  float rBC; //reading bell-curved.
  if((cameraCurrentReading &lt; limitLower) || (cameraCurrentReading &gt; limitUpper)) {
    //the slider has moved enough to justify new calculation...
    readingAdjusted = accountForSliderZones(cameraCurrentReading,cameraStickDeadZone,cameraHardValue);  //remember this will return 0 if in deadzone.
    //rBC = pow(readingAdjusted, 2) * cameraMaxStep * -1.0;
    rBC = pow(readingAdjusted, 3) * cameraMaxStep;
    //rBC = readingAdjusted * cameraMaxStep; //linear
    if(flipCameraStickX) {
      rBC = rBC * -1.0;
    }
    if((rBC &lt; 0.0) &amp;&amp; (rBC &gt; -1.0)) {
      rBC = rBC - cameraMinStep;  //make sure the movement is always a minimum amt.
    } else if ((rBC &gt; 0.0) &amp;&amp; (rBC &lt; 1.0)) {
      rBC = rBC + cameraMinStep; 
    } else {
      //rBC is 0.0
    }
    cameraLastReading = cameraCurrentReading;
    cameraStep = int(rBC);   //the step size of camera movement.
  }
  moveCamera(cameraStep);
}
void sliderHandling() {
  //controller-specific
  if (hasSlidingClutch) {
    shClutch();
  }
  if (hasCameraStick) {
    shCamera();
  }
&nbsp;
  //common
  shThrottle();
  shWheel();
}
////////////////////////////////////////////
//                                        //
//            Button Handling             //
//                                        //
////////////////////////////////////////////
void bhStart()
{
  //START BUTTON
  if (buttonStart.pressed() &amp;&amp; !buttonStartPressed &amp;&amp; !clutchIn) {
    startVehicle();
    buttonStartPressed = true;
  } else if (!buttonStart.pressed() &amp;&amp; buttonStartPressed) {
    buttonStartPressed = false;
  }
}
void bhSendSettings()
{
  //Send Settings BUTTON
  if (buttonSendSettings.pressed() &amp;&amp; !buttonSendSettingsPressed &amp;&amp; clutchIn) {
    sendSettings();
    buttonSendSettingsPressed = true;
  } else if (!buttonSendSettings.pressed() &amp;&amp; buttonSendSettingsPressed) {
    buttonSendSettingsPressed = false;
  }
}
void bhESC()
{
  //ESC ON/OFF BUTTON
  if (buttonESC.pressed() &amp;&amp; buttonESCpressed == false) {
    escPOWER();
    buttonESCpressed = true;
  } else if (!buttonESC.pressed() &amp;&amp; buttonESCpressed == true) {
    buttonESCpressed = false;
  } 
}
void bhResetOnFlatline()
{
  //RESET ON FLATLINE BUTTON
  if (buttonResetOnFlatline.pressed() &amp;&amp; !buttonResetOnFlatlinePressed &amp;&amp; clutchIn) {
    resetOnFlatline();
    buttonResetOnFlatlinePressed = true;
  } else if (!buttonResetOnFlatline.pressed() &amp;&amp; buttonResetOnFlatlinePressed &amp;&amp; !clutchIn) {
    buttonResetOnFlatlinePressed = false;
  }
}
void bhCentreCamera()
{
    //This button centres the camera.
    //and acts like a clutch for camera centre trim adjustment...
    if (buttonCentreCamera.pressed() &amp;&amp; !buttonCentreCameraPressed) {
      buttonCentreCameraPressed = true;
      centreCamera();
    } else if (!buttonCentreCamera.pressed() &amp;&amp; buttonCentreCameraPressed) {
      buttonCentreCameraPressed = false;
    }
}
void bhAutoCamera()
{
    //This button centres the camera.
    //and acts like a clutch for camera centre trim adjustment...
    autocameraOn = false; //made false in case of communication problems.
    if (buttonAutoCamera.pressed() &amp;&amp; !buttonAutoCameraPressed) {
      buttonAutoCameraPressed = true;
      autoCamera();
    } else if (!buttonAutoCamera.pressed() &amp;&amp; buttonAutoCameraPressed) {
      buttonAutoCameraPressed = false;
    }
}
void bhClutch()
{
  //Clutch in or out?
  if (buttonCLUTCH.pressed()) {
    clutchIn = true;
  } else if (!buttonCLUTCH.pressed()) {
    clutchIn = false;
  }
}
void adjustReverseSpeed() {
  if(gear &gt; 2) {
    throttleMaxReverse = throttleRevMaxDefault;  
  } else if(gear &lt; 3) {
    throttleMaxReverse = (throttleRevMaxDefault * 2) / 3;  
  }
}
void bhGearUP()
{
  if(gear &lt; numberOfGears) {
    gear++;
    throttleMaxForward = throttleMax * (gear / numberOfGears);
    println(&quot;Gear: &quot; + gear + &quot;, throttleMaxForward = &quot; + throttleMaxForward);
  }
  adjustReverseSpeed();
}
void bhGearDOWN() 
{
  if(gear &gt; 1) {
    gear--;
    throttleMaxForward = throttleMax * (gear / numberOfGears);
    println(&quot;Gear: &quot; + gear + &quot;, throttleMaxForward = &quot; + throttleMaxForward);
  }
  adjustReverseSpeed();
}
void bhGearUpOrDown() 
{
  if(clutchIn || !clutchForGears || (hasForRevGear &amp;&amp; direction == 0)) {
    //Gear up
    if (buttonGEARUP.pressed() &amp;&amp; buttonGEARUPpressed == false) {
      bhGearUP();
      buttonGEARUPpressed = true; 
    } else if (!buttonGEARUP.pressed() &amp;&amp; buttonGEARUPpressed == true) {
      buttonGEARUPpressed = false; 
    }
    //gear down
    if (buttonGEARDOWN.pressed() &amp;&amp; buttonGEARDOWNpressed == false) {
      bhGearDOWN();
      buttonGEARDOWNpressed = true; 
    } else if (!buttonGEARDOWN.pressed() &amp;&amp; buttonGEARDOWNpressed == true) {
      buttonGEARDOWNpressed = false; 
    }
  }
}
void bhDirection()
{
  //handles gearshifter on g27...
  //buttonForward and buttonReverse ARE ALREADY defined, just not used here for now.
  if(buttonGEAR1.pressed() || buttonGEAR2.pressed() || buttonGEAR3.pressed()) {
    if(buttonForwardPressed == false) {
      direction = 1;  //0 = neutral, 1 = forward, 2 = reverse.
      buttonForwardPressed = true;
      buttonReversePressed = false;
      //println(&quot;FORWARD&quot;);
    }
  } else if (buttonGEAR2.pressed() || buttonGEAR4.pressed() || buttonGEAR6.pressed()) {
    if(buttonReversePressed == false) {
      direction = 2;  //0 = neutral, 1 = forward, 2 = reverse.
      buttonForwardPressed = false;
      buttonReversePressed = true;
      //println(&quot;REVERSE&quot;);
    }
  } else {
    if(direction != 0) {
      direction = 0; //0 = neutral, 1 = forward, 2 = reverse.
      buttonForwardPressed = false;
      buttonReversePressed = false;
      throttleSetting = throttleNeutral;
      //lastBrakeSetting = throttleNeutral;
      //println(&quot;NEUTRAL&quot;);
    }  
  }
}
void bhCalibrateSensors()
{
  if(buttonCalibrateSensors.pressed() &amp;&amp; !buttonCalibrateSensorsPressed &amp;&amp; clutchIn) {
    calibrateSensors(); 
    buttonCalibrateSensorsPressed = true;
  } else if (!buttonCalibrateSensors.pressed() &amp;&amp; buttonCalibrateSensorsPressed) {
    buttonCalibrateSensorsPressed = false;
  }
}
void bhSendFFaccels()
{
  if(buttonSendFFaccels.pressed() &amp;&amp; !buttonSendFFaccelsPressed &amp;&amp; !clutchIn) {
    ffSendAccels(); 
    buttonSendFFaccelsPressed = true;
  } else if (!buttonSendFFaccels.pressed() &amp;&amp; buttonSendFFaccelsPressed) {
    buttonSendFFaccelsPressed = false;
  }
}
void bhCamera() {
  if(buttonCameraMoveLeft.pressed() &amp;&amp; buttonCameraMoveRight.pressed()) {
    if(!buttonCameraMoveLeftPressed || !buttonCameraMoveRightPressed) {
      buttonCameraMoveLeftPressed = true;
      buttonCameraMoveRightPressed = true;
      centreCamera();
    }
  } else if(buttonCameraMoveLeft.pressed()) {
    int negativeStep = int(cameraMinStep) * -1;
    moveCamera(negativeStep); 
    buttonCameraMoveLeftPressed = true;
  } else if (!buttonCameraMoveLeft.pressed() &amp;&amp; buttonCameraMoveLeftPressed) {
    buttonCameraMoveLeftPressed = false;
  } else if (buttonCameraMoveRight.pressed()) {
    int intStep = int(cameraMinStep);
    moveCamera(intStep); 
    buttonCameraMoveRightPressed = true;
  } else if (!buttonCameraMoveRight.pressed() &amp;&amp; buttonCameraMoveRightPressed) {
    buttonCameraMoveRightPressed = false;
  }
}
void buttonHandling()
{
  //controller-specific
  if(!hasSlidingClutch) {
    //if a button is used to activate the clutch, call the bhClutch function.
    bhClutch();        //button-handled clutch
  } 
  if (hasForRevGear) {
    //does the controller have a button that indicates the direction the
    //model should be moving? On the G27 it's the gearshifter.
    bhDirection();     //button for direction handling. Forward/Reverse.
  }
  if (hasCameraButtons) {
    bhCamera();
  }
  if (hasCameraCentreButton) {
    bhCentreCamera();
  }
&nbsp;
  //Common
  bhAutoCamera();
  bhSendSettings();
  bhCalibrateSensors();
  bhSendFFaccels();
  bhGearUpOrDown();  //gear up or down.
  bhResetOnFlatline(); //switch for turning on or off automatic reset on flatline.
  bhESC();    //ESC button  
  bhStart();  //start button
}
&nbsp;
////////////////////////////////////////////
//                                        //
//          CoolieHat Handling            //
//          Including cooliehats broken   //
//          into buttons                  //
//                                        //
////////////////////////////////////////////
void coolieLeft()
{
  if(buttonCOOLIELEFTpressed &amp;&amp; !cooliePressedOnce) {
    //the clutch needs to be active to adjust the steering trim.
    cooliePressedOnce = true;
    adjustSteeringTrim(-1);
    adjustCameraTrim(-1);
    delay(400);
  } else if (!buttonCOOLIELEFTpressed &amp;&amp; cooliePressedOnce) {
  }
}
void coolieRight()
{
  if(buttonCOOLIERIGHTpressed &amp;&amp; !cooliePressedOnce) {
    //the clutch needs to be active to adjust the steering trim.
    cooliePressedOnce = true;
    adjustSteeringTrim(1);
    adjustCameraTrim(1);
  } else if (!buttonCOOLIERIGHTpressed &amp;&amp; cooliePressedOnce) {
  }
}
void coolieUp()
{
&nbsp;
}
void coolieDown()
{
&nbsp;
}
void fauxCoolieHatHandling()
{
    if(coolieLEFT.pressed() &amp;&amp; !buttonCOOLIELEFTpressed) {
      buttonCOOLIELEFTpressed = true;
      coolieLeft();
    } else if (!coolieLEFT.pressed() &amp;&amp; buttonCOOLIELEFTpressed) {
      buttonCOOLIELEFTpressed = false;
      cooliePressedOnce = false;
    } else if (coolieRIGHT.pressed() &amp;&amp; !buttonCOOLIERIGHTpressed) {
      buttonCOOLIERIGHTpressed = true;
      coolieRight();
    } else if (!coolieRIGHT.pressed() &amp;&amp; buttonCOOLIERIGHTpressed) {
      buttonCOOLIERIGHTpressed = false;
      cooliePressedOnce = false;
    } else if (coolieUP.pressed() &amp;&amp; !buttonCOOLIEUPpressed) {
      buttonCOOLIEUPpressed = true;
      coolieUp();
    } else if (!coolieUP.pressed() &amp;&amp; buttonCOOLIEUPpressed) {
      buttonCOOLIEUPpressed = false;
      cooliePressedOnce = false;
    } else if (coolieDOWN.pressed() &amp;&amp; !buttonCOOLIEDOWNpressed) {
      buttonCOOLIEDOWNpressed = true;
      coolieDown();
    } else if (!coolieDOWN.pressed() &amp;&amp; buttonCOOLIEDOWNpressed) {
      buttonCOOLIEDOWNpressed = false;
      cooliePressedOnce = false;
    } 
}
void coolieHatHandling()
{
  //attempting to map a cooliehat
  if (cooliehat.pressed()) {
    float coolieXvalue = cooliehat.getX();
    float coolieYvalue = cooliehat.getY();
    if(coolieXvalue &lt; 0) {
      buttonCOOLIELEFTpressed = true;
      coolieLeft();
    } else if (coolieXvalue &gt; 0) {
      buttonCOOLIERIGHTpressed = true;
      coolieRight();
    } else if (coolieYvalue &lt; 0) {
      buttonCOOLIEUPpressed = true;
      coolieUp();
    } else if (coolieYvalue &gt; 0) {
      buttonCOOLIEDOWNpressed = true;
      coolieDown();
    }
    cooliehatPressed = true;
  } else if (!cooliehat.pressed() &amp;&amp; cooliehatPressed) {
    //cooliehat is not pressed, execute &quot;released&quot; functions
    buttonCOOLIELEFTpressed = false;
    buttonCOOLIERIGHTpressed = false;
    buttonCOOLIEUPpressed = false;
    buttonCOOLIEDOWNpressed = false;
    coolieLeft();
    coolieRight();
    coolieUp();
    coolieDown();
    cooliehatPressed = false;
    cooliePressedOnce = false;
  }
}
&nbsp;
////////////////////////////////////////////
//                                        //
//            Orchestrate                 //
//            DEVICE READING              //
//                                        //
////////////////////////////////////////////
void readControlDevice() {
  //if using this controller, execute these params, etc.
  sliderHandling();
  buttonHandling();
  if(hasCoolieHat &amp;&amp; coolieIsCoolie) {
    coolieHatHandling();
  } else if (hasCoolieHat &amp;&amp; !coolieIsCoolie) {
    //cooliehat is broken into buttons.
    fauxCoolieHatHandling();
  }
}
&nbsp;
////////////////////////////////////////////
//                                        //
//                 LOOP                   //
//                                        //
////////////////////////////////////////////
void draw()
{
  heartbeat();
  if(hasRumblers &gt; 0) {
    //if the controller has rumblers, call handle rumbling...
    handleRumbling();
  }
  readControlDevice();
  sendCom();
}</pre></td></tr></table></div>

</div>
]]></content:encoded>
			<wfw:commentRss>http://www.blairkelly.ca/2012/04/20/arduino-wifly-mini-processing-code/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>A Wii Power Supply Problem</title>
		<link>http://www.blairkelly.ca/2012/03/15/a-wii-power-supply-problem/</link>
		<comments>http://www.blairkelly.ca/2012/03/15/a-wii-power-supply-problem/#comments</comments>
		<pubDate>Thu, 15 Mar 2012 18:22:38 +0000</pubDate>
		<dc:creator>blair</dc:creator>
				<category><![CDATA[Blog]]></category>
		<category><![CDATA[DIY]]></category>
		<category><![CDATA[Technology]]></category>
		<category><![CDATA[Video Games]]></category>
		<category><![CDATA[Broken]]></category>
		<category><![CDATA[Power]]></category>
		<category><![CDATA[Supply]]></category>
		<category><![CDATA[Wii]]></category>

		<guid isPermaLink="false">http://www.blairkelly.ca/?p=2690</guid>
		<description><![CDATA[Is your Wii power supply acting up? Have a look at this before you take it apart or buy a new one.]]></description>
			<content:encoded><![CDATA[<p>In her hard-earned leisure time, my wife occasionally enjoys playing on our Wii system. She came to me the other day and said it wouldn&#8217;t turn on.</p>
<p>I had a look and indeed the Wii appeared to be deceased, so I removed it to my office. First I plugged in the power supply and checked the leads with my multimeter. No voltage. Then I attached a 12 volt battery to the Wii, and on it blinked and the optical drive jerked to life. Problem isolated. The next thing I did was &#8211; what else? &#8211; go to Google to find a solution.</p>
<p>I&#8217;m very glad to have found this blog post by Damon Kohler: <a href="http://www.damonkohler.com/2009/07/broken-wii-power-supply.html">Is your Wii power supply broken?</a></p>
<p>Mr. Kohler had the misfortune of taking apart his entire Wii power supply, only to find there was nothing wrong with it. He put it back together and it worked. He suggested letting the power supply some time to rest; that is, to lose any residual charge in its capacitors and subsequently any memory it might have. He also recommended shorting the pins to help guarantee a reset.</p>
<p>I did this, and seconds later the Wii was up and running. My guess is the power supply has some kind of surge or error protection that causes a shutdown. Or maybe it&#8217;s just a design flaw. Anyway, before you take apart your power supply or go and buy a new one, make sure you try the above.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.blairkelly.ca/2012/03/15/a-wii-power-supply-problem/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>First Impressions: Chris Hedges</title>
		<link>http://www.blairkelly.ca/2011/12/30/first-impressions-chris-hedges/</link>
		<comments>http://www.blairkelly.ca/2011/12/30/first-impressions-chris-hedges/#comments</comments>
		<pubDate>Fri, 30 Dec 2011 21:41:07 +0000</pubDate>
		<dc:creator>blair</dc:creator>
				<category><![CDATA[Argument]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Commentary]]></category>
		<category><![CDATA[In the Media]]></category>
		<category><![CDATA[Social Issues]]></category>
		<category><![CDATA[Chris]]></category>
		<category><![CDATA[First]]></category>
		<category><![CDATA[Hedges]]></category>
		<category><![CDATA[Impressions]]></category>
		<category><![CDATA[Lies]]></category>
		<category><![CDATA[Nuance]]></category>
		<category><![CDATA[Truth]]></category>

		<guid isPermaLink="false">http://www.blairkelly.ca/?p=2563</guid>
		<description><![CDATA[After deconstructing the first words I ever heard the man speak, I would use terms such as "intellectually dishonest," "easily bruised" and "contents under pressure" to describe Chris Hedges.]]></description>
			<content:encoded><![CDATA[<p>A friend of mine brought to my attention a recent commenting and light discussion, featured on the CBC&#8217;s radio show &#8220;Day 6,&#8221; on the passing of author and journalist Christopher Hitchens. The two guests were Paul Wolfowitz and Chris Hedges, the former praised with great sadness the life of the late polymath, and the latter strongly criticised him. This occasioned the first time I can recall consciously registering the name &#8220;Chris Hedges,&#8221; who had some rather alarming things to say about Hitch: that he was without a moral core, lacked an appreciation for nuance, was a bully, and even that he defended the extermination of native Americans.</p>
<p>I have transcribed the portions of Hedges interview that I found most concerning, and have tried to give enough context to be fair. (<a href="http://www.cbc.ca/day6/2011/12/16/chris-hedges-on-christopher-hitchens/" target="_blank">Click here</a> to listen to Hedges say it all himself.)</p>
<p style="font-style: italic; padding-left: 45px; padding-right: 55px;">Hitchens was always out for Hitchens. He had a great mind and he was an exceptionally talented writer, but I never found any moral core there. Even on the left he loved to sort of throw bombshells defending the European invasion of North America, and the extermination of native Americans, or suddenly deciding he was against abortion. He loved the publicity that comes with being a contrarian.</p>
<p>I&#8217;m unsure whether the formula &#8220;so-and-so was only ever out for his- or her-self&#8221; holds water in any context. (I&#8217;ve <a href="http://www.samharris.org/site/full_text/the-moral-landscape/" title="I read them here, in Sam Harris' excellent 'The Moral Landscape'" target="_blank">read</a> persuasive arguments that claim the only way to be selfish in a positive way might be to help others.) In any case, Hitchens was outspoken for the rights of women and homosexuals and has countless other endeavours tied to his name that trash Hedges&#8217; obscure pronouncement altogether.</p>
<p>Regarding his statement on Hitchens&#8217; villainy, the sneaky Mr. Hedges charges us with a form of the &#8220;rotten-core cinnamon bun&#8221; problem. (It goes like this: presented to you is a cinnamon bun that is normal in every way except that it has had inserted into it, post-bake, a gooey centre of dog shit. You are then asked: do you partake of the fresh, unsoiled exterior? Even if you are unlike me and are fond of cinnamon buns, I probably know your answer.) Hedges is trying to perform some mental trickery by underlining Hitchens&#8217; great mind and excellent writing ability and then besmirching both with an &#8220;immoral core.&#8221; It amounts to saying &#8220;there were great things about Hitchens, but they weren&#8217;t great, and in fact he was a bad person.&#8221;</p>
<p>I&#8217;ll come back to the accusation of support for genocide later. Here are some more words from Hedges&#8217;, this time on Hitchens&#8217; worldview:</p>
<p style="font-style: italic; padding-left: 45px; padding-right: 55px;">When you have that kind of regidity to any ideological system, whether it&#8217;s on the left or the right, you just replace a few words here and there and bifurcate the world into us and them, black and white, good and evil&#8230; and I think that is a sign of, finally, a kind of great intellectual failing and inability to deal with nuance &#8211; ambiguity &#8211; and I think that was part of his deficit.</p>
<p>I could not illustrate the irony of Hedges&#8217; words better than has already been done by Sam Harris. (<a href="http://www.samharris.org/blog/item/response-to-chris-hedges/" title="Dear Angry Lunatic: A Response to Chris Hedges" target="_blank">&#8220;Nuance is really what one hopes Hedges would discover once in his life—if for no other reason than it would leave him with nothing left to say.&#8221;</a>) However I was able with my own limited perspective to see a hole in Hedges&#8217; impression. It took one look at a single book title from each author on (roughly) the same subject, a) Hitchens&#8217; &#8220;god Is Not Great: How Religion Poisons Everything&#8221; and b) Hedges&#8217; &#8220;I Don&#8217;t Believe in Atheists.&#8221; With only the titles, we see Hitchens targets a belief system, and Hedges targets an entire group of people as if it were homogeneous. (What else does that remind you of?) Tip of the iceberg, I know, but I wanted to point out, since I don&#8217;t think it should be underestimated, that whiffs of intellectual Swiss-cheese are available for a <i>mere glance</i> at Hedges&#8217; work. Anyway, let&#8217;s get on with more of what Hedges&#8217; had to say:</p>
<p style="font-style: italic; padding-left: 45px; padding-right: 55px;">I found [Hitchens] when he was on the left to be a bully, I didn&#8217;t like the way he would frame debates, it always became personal, we saw this when he switched sides and he began to tear into figures that he had once revered; Noam Chomsky, Edward Said and others.</p>
<p>As I transcribed the above words, I asked myself if they really needed to be refuted. How then would Hedges have Hitchens frame debates? Would he like it better if we pretended that we weren&#8217;t accountable for the views we espouse? If someone expresses support for suicide-murderers blowing themselves up on packed city buses, I&#8217;m going to hold that view against the person in question and I hope everyone else in their right mind would, too! Not to belabour the point, but what ever could Hedges <i>possibly mean</i> by his complaint? And same goes for criticising people you might have once revered. If at any point you hold a deep respect for someone, does this mean of this person you forfeit your right to lay anything but positive judgement?</p>
<p>Later, Hedges moves himself into the territory of the totally nonsensical:</p>
<p style="font-style: italic; padding-left: 45px; padding-right: 55px;">[Hitchens] used a secular vocabulary to bifurcate the world into these binary poles of black and white, us and them. So his political agenda didn&#8217;t stray very far from the Christian right: the Christian right wants to drop iron fragmentation bombs all over the Middle East because Islam is a satanic religion, he wants to do it because they&#8217;re barbarians&#8230; what he really promoted was a secular fundamentalism.</p>
<p>I have three issues with this tripe. 1) If by &#8220;us&#8221; Hedges is referring to our secular tradition with courts of law and rights for humankind and even other animals, and by &#8220;them&#8221; he is referring to the Taliban and their kind who throw acid in the eyes of &#8220;disobedient&#8221; women and mutilate the labia of young girls, then I get <i>absolutely nauseous</i> at the idea of <i>anything other</i> than an &#8220;us and them&#8221; dichotomy. I want &#8220;them&#8221; to be scrubbed from the face of the Earth. If they&#8217;d rather die than discontinue blowing up apostates, <i>so be it.</i> I am very suspicious of the moral integrity of anybody who suggests I should accommodate <i>in any way</i> psychopaths like the Taliban. <i>No italics could stress my feeling on this point enough.</i> 2) The next bit of bile coughed up by Hedges needs to be dealt with in its entirety: &#8220;the Christian right wants to drop iron fragmentation bombs all over the Middle East because Islam is a satanic religion, he wants to do it because they&#8217;re barbarians.&#8221; As Hitchens said himself (<a href="http://www.fpp.co.uk/online/99/02/Hitchens.html" target="_blank">of another matter</a>), to respond to this would be a bit like responding to the question &#8220;so when did you stop beating your wife?&#8221; <a href="http://www.youtube.com/watch?v=dddAi8FF3F4" title="It's a trap." target="_blank">It&#8217;s a trap,</a> and in this case Hedges&#8217; statement is so nonsensical it&#8217;s not even wrong. 3) Finally, if words are to mean anything at all, then &#8220;secular fundamentalism&#8221; is a term that makes absolutely no sense whatsoever. It is a popular argument bandied by those who are not well-versed in the debate about the falsity and negativity of religion, and by those who are so loose with their definitions that words become transparent. This issue has been expertly addressed elsewhere. In his essay &#8220;Can an Atheist Be a Fundamentalist?&#8221;, A.C. Grayling has this to say:</p>
<p style="font-style: italic; padding-left: 45px; padding-right: 55px;">It is also time to put to rest the mistakes and assumptions that lie behind a phrase used by some religious people when talking of those who are plain-spoken about their disbelief in any religious claims: the phrase &#8220;fundamentalist atheist [Hedges' 'secular fundamentalist'].&#8221; What would a non-fundamentalist atheist be? Would he be someone who believed only somewhat that there are no supernatural entities in the universe &#8211; perhaps that there is only part of a god (a divine foot, say, or a buttock)? Or that gods exist only some of the time &#8211; say, Wednesdays and Saturdays? (That would not be so strange: for many unthinking quasi-theists, a god exists only on Sundays.) Or might it be that a non-fundamentalist atheist is one who does not mind that other people hold profoundly false and primitive beliefs about the Universe, on the basis of which they have spent centuries mass-murdering other people who do not hold exactly the same false and primitive beliefs as themselves &#8211; and still do?</p>
<p>At last, I want to come back to Hedges&#8217; assertion that Hitchens supported the extermination of native American peoples. Hedges is referring to the contents of an article Hitchens wrote for The Nation&#8217;s &#8220;Minority Report&#8221; column, published on October 19th, 1992. (It can be <a href="http://www.thenation.com/archive/minority-report-220" target="_blank">obtained here</a>.) To say the author&#8217;s words were taken out of context seems almost too obvious to mention. In my search for the original document I <a href="http://www.abc.net.au/unleashed/3737110.html" target="_blank">found an article</a> (depressingly, on the Australian Broadcasting Corporation&#8217;s website) by Michael Brull, who apparently shares Hedges&#8217; questionable and unfavourable opinion of Hitchens. Presuming Hedges would agree with Brull&#8217;s selection of quotes (since there is nothing else in the article I can see that would easily lend itself to being taken out of context), I present to you his interpretation of Hitchens&#8217; remarks:</p>
<p style="font-style: italic; padding-left: 45px; padding-right: 55px;">In October 1992, Hitchens explained in The Nation that his &#8220;old comrade, David Dellinger&#8221; – one of the most extraordinary and inspiring men of the last century – had phoned to inform Hitchens of his impending protest on Colombus [sic] Day. Hitchens rejected this protest. Hitchens was not sure whether such a protest was &#8220;merely risible or faintly sinister&#8221;. Such a protest is sinister &#8220;because it is an ignorant celebration of stasis and backwardness, with an unpleasant tinge of self-hatred&#8221;.<br/><br />
&#8220;1492 was a very good year,&#8221; Hitchens impatiently explained, and &#8220;deserves to be celebrated with great vim and gusto.&#8221;<br/><br />
Those &#8220;who view the history of North America as a narrative of genocide and slavery&#8221; fail to understand that this is &#8220;the way that history is made, and to complain about it is as empty as complaint about climatic, geological or tectonic shift&#8221;. <b>The annihilation of the Native Americans was an instance</b> that left &#8220;humanity on a slightly higher plane than it knew before&#8221;, inaugurating an &#8220;early boundless epoch of opportunity and innovation&#8221;.</p>
<p>Confusing insertion of &#8220;impatience&#8221; into Hitchens&#8217; cadence aside (where did he get <i>that</i> from?), I have bolded Brull&#8217;s lie. Nowhere in Hitchens&#8217; article does he say &#8220;The annihilation of the Native Americans was an instance that left humanity on a slightly higher plane.&#8221; It simply is not there. Read it for yourself and you will see Hitchens makes mention of the pogroms various Nations had against each other before the arrival of the &#8220;palefaces.&#8221; He never denies the negative impact of the latest foreigners to arrive on what is now the American continent in 1492. Hitchens says:</p>
<p style="font-style: italic; padding-left: 45px; padding-right: 55px;">&#8230;those who view the history of North America as narrative of genocide and slavery are, it seems to me, hopelessly stuck on this reactionary position. They can think of the Western expansion of the United States only in terms of plague blankets, bootleg booze and dead buffalo, never in terms of the medicine chest, the wheel and the railway.<br/><br />
One need not be an automatic positivist about this. But it does happen to be the way that history is made, and to complain about it is as empty as complaint about climatic, geological or tectonic shift.<br/><br />
&#8230;As Marx wrote about India, the impact of a more developed society upon a culture (or a series of warring cultures, since there was no such nation as India before the British Empire) can spread aspects of modernity and enlightenment that outlive and transcend the conqueror. This isn&#8217;t always true; the British probably left Africa worse off than they found it, and they certainly retarded the whole life of Ireland. But it is sometimes unambiguously the case that a certain coincidence of ideas, technologies, population movements and politico-military victories leaves humanity on a<br />
slightly higher plane than it knew before. The transformation of part of the northern part of this continent into “America” inaugurated an early boundless epoch of opportunity and innovation, and thus deserves to be celebrated with great vim and gusto, with or without the participation of those who wish they had never been born.</p>
<p>If you read the entire article, which I highly recommend, you will find fully confirmed what you see above; no endorsement of genocide &#8211; nothing of the kind &#8211; and a nuanced, thoughtful analysis of history that amounts in its most distilled form to &#8220;overall, the migration of European people to the Americas was a good thing.&#8221; I find it terribly disappointing that such respectable institutions as the CBC and ABC would have the likes of Hedges or Brull on any of their programs; these men don&#8217;t just hold a different point of view, they maintain dishonest opinions. The most that this kind of dishonesty should help a public figure attain is standing on upturned milk crates at street corners, screaming at pigeons.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.blairkelly.ca/2011/12/30/first-impressions-chris-hedges/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Beyond &#8216;Oops&#8217;</title>
		<link>http://www.blairkelly.ca/2011/12/09/beyond-oops/</link>
		<comments>http://www.blairkelly.ca/2011/12/09/beyond-oops/#comments</comments>
		<pubDate>Fri, 09 Dec 2011 18:31:06 +0000</pubDate>
		<dc:creator>blair</dc:creator>
				<category><![CDATA[Argument]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Commentary]]></category>
		<category><![CDATA[Global Issues]]></category>
		<category><![CDATA[In the Media]]></category>
		<category><![CDATA[Religion]]></category>
		<category><![CDATA[Social Issues]]></category>
		<category><![CDATA[Extremism]]></category>
		<category><![CDATA[Perry]]></category>
		<category><![CDATA[Poison]]></category>
		<category><![CDATA[Religious]]></category>
		<category><![CDATA[Rick]]></category>
		<category><![CDATA[Strong]]></category>

		<guid isPermaLink="false">http://www.blairkelly.ca/?p=2517</guid>
		<description><![CDATA[Rick Perry has outdone himself.]]></description>
			<content:encoded><![CDATA[<p>When I learned of him, I thought Rick Perry was another goofy and deeply religious Christian &#8211; <a target="_blank" href="http://en.wikipedia.org/wiki/George_W._Bush">the kind we&#8217;ve seen before</a> &#8211; that might do well in the upcoming US election. With his <a href="http://www.youtube.com/watch?feature=player_embedded&#038;v=0PAJNntoRgA" target="_blank">latest video statement</a>, the governor of Texas has revealed he may be a lot more dangerous than that. According to Perry, we &#8220;know there&#8217;s something wrong in this country when gays can serve openly in the military but our kids can&#8217;t openly celebrate Christmas or pray in school.&#8221;</p>
<p>This is both a non squitur and a lie. First, sexual orientation and religion are not as similar as thought by Perry or those who perform <a href="http://en.wikipedia.org/wiki/Gay_exorcism">Gay exorcisms</a>. Second, children <i>can</i> celebrate Christmas and pray in public school; these simply aren&#8217;t part of the school&#8217;s agenda. And that&#8217;s as it should be. The United States is a pluralistic nation, and it is that way <i>in spite</i> of religion. It&#8217;s not perfect, but it works as well as it does with great thanks to the authors of what is undoubtedly one of the most important documents in all of human history: the US Constitution. In it, a clear line is drawn between church and state. The founding fathers were aware of the consequences of giving religion too much say in the affairs of the state. When church and state are bound together things have a history of getting out of hand: those who believe they have god on their side tend to make a duty of <a target="_blank" href="http://en.wikipedia.org/wiki/Giles_Corey">torturing and murdering people</a> for &#8216;crimes in the eyes of the faith.&#8217; And for instruction on how religion influences education and learning, you might recall <a target="_blank" href="http://en.wikipedia.org/wiki/Galileo_Galilei" target="_blank">Galileo</a> or, more recently, the <a href="http://en.wikipedia.org/wiki/Scopes_Trial" target="_blank">Scopes Monkey Trial</a>. And human rights? The Mormon church was an <a href="http://en.wikipedia.org/wiki/Black_people_and_The_Church_of_Jesus_Christ_of_Latter-day_Saints" target="_blank">officially racist institution</a> up to 1978, long after the great civil rights movements of the sixties. And to this day, opposition in the United States to gay marriage, and homosexuality generally, <a href="http://pewforum.org/Gay-Marriage-and-Homosexuality/Religious-Beliefs-Underpin-Opposition-to-Homosexuality.aspx" target="_blank">is religiously motivated</a>. These are mere examples plucked from a selection that, were each written on cards and put in a pile, the result might be a hazard to passing aircraft.</p>
<p>Religion doesn&#8217;t strengthen America; it weakens it. Religion is corrosive to everything an enlightened society stands for and the temptation to be lulled into its traps that offer false promises with immoral premises must be resisted to the maximum of our abilities. That a deeply religious and prominent Texan politician &#8211; a presidential candidate &#8211; appeared on a major media outlet decrying the equality rightly granted to homosexuals, and specifically offering secularism as the &#8216;culprit,&#8217; is to me a sign that the battle for reason against blind dogma might be heating up even more than it has already in recent years.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.blairkelly.ca/2011/12/09/beyond-oops/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Resize Images with PHP</title>
		<link>http://www.blairkelly.ca/2011/11/25/resize-images-with-php/</link>
		<comments>http://www.blairkelly.ca/2011/11/25/resize-images-with-php/#comments</comments>
		<pubDate>Fri, 25 Nov 2011 20:13:04 +0000</pubDate>
		<dc:creator>blair</dc:creator>
				<category><![CDATA[Blog]]></category>
		<category><![CDATA[WebDev]]></category>
		<category><![CDATA[Wordpress]]></category>
		<category><![CDATA[Function]]></category>
		<category><![CDATA[Image]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Resize]]></category>

		<guid isPermaLink="false">http://www.blairkelly.ca/?p=2494</guid>
		<description><![CDATA[Here's a PHP function to resize a .jpg or .png file.]]></description>
			<content:encoded><![CDATA[<p>Here&#8217;s a function that will take a JPG or PNG image file and resize it to the dimensions you want. It maintains the aspect ratio of the original file even if the dimensions do not match, so anything that falls outside of the sample area will be cropped. I wrote this function for use with WordPress, but if you&#8217;re running PHP it wouldn&#8217;t be hard to adapt for your purposes. I use the original image ID (given by WordPress) to name the new file. You obviously don&#8217;t have to use this. If you do, <a href="http://www.blairkelly.ca/2011/11/24/get-attachment-id-from-source-url/" title="Get Image ID from URL">refer to my last post for a function</a> that will return the ID of an image based on a given URL.</p>
<div id="codecontainer">

<div class="wp_syntax"><table><tr><td class="line_numbers"><pre>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
</pre></td><td class="code"><pre class="language" style="font-family:monospace;">function fixedCropImage($originalImage, $newImageLoc, $originalImageID, $destwidth, $destheight) {
	//get the blog URL and add a slash to the end.
	$theWPurl = get_bloginfo('wpurl').'/';
	//find the relative path of the original file. This is useful if you use a testing server.
	$originalFileRelativePath = str_replace($theWPurl, '', $originalImage);
	//check to see if the file we're asking to resize exists.
	if (file_exists($originalFileRelativePath) == FALSE) {
		//if it doesn't, point to a default image.
		$originalImage = 'images/default.jpg'; //PATH MUST BE RELATIVE FOR file_exists to work!
	}
	//gather basic info about the original file, for use in making the path of the new file.
	$path_parts = pathinfo($originalImage);
	//create the path of the new file.
	$theNewImage = 'wp-content/'.$newImageLoc.'/'.$originalImageID.'-'.$destwidth.'x'.$destheight.'.'.$path_parts['extension'];
	//first, check to see if this file exists already. If it does, then don't do anything. Just return the location of the resized file.
	if (file_exists($theNewImage) == FALSE) //check to see if the file already exists. if it does, then don't perform the function!
	{	
		//dest ratio - the image dimension ratio of desired destination image!
		$destratio = $destwidth / $destheight;
		// Create image instance of $src.
		if(strtolower($path_parts['extension']) == &quot;jpg&quot; || strtolower($path_parts['extension']) == &quot;jpeg&quot;){
			$src = imagecreatefromjpeg($originalImage);
		} else if(strtolower($path_parts['extension']) == &quot;png&quot;){
			$src = imagecreatefrompng($originalImage);
		}
		//create instance of $dest file.
		$dest = imagecreatetruecolor($destwidth, $destheight);
		// Ifs - determine the layout of the image, portrait or landscape.
		$imageSize = getimagesize($originalImage); //returns array, 0 width, 1 height.
		$sourceRatio = $imageSize[0]/$imageSize[1];
		//determine size of sample!
		if($destratio &lt; $sourceRatio) {
			//if the destination ratio is smaller than the source, we must use the whole height of the image and a portion of the width.
			$sampleY = round($imageSize[1]);
			$sampleX = round($sampleY * $destratio);
			//this means the sample must start on the y axis at 0, and on the x axis, somewhere in between.
			$startY = 0;
			$startX = ($imageSize[0] - $sampleX) / 2; //take sample from the middle of X axis
		} else {
			//the destination ratio is larger than the source. use whole X width and a portion of the source height.
			$sampleX = round($imageSize[0]); //always use 100% of the sample width in this case.
			$sampleY = round($sampleX / $destratio);
			//this means the sample must start on the x axis at 0, and on the y axis, somewhere in between.
			$startX = 0;
			$startY = ($imageSize[1] - $sampleY) / 2; //take sample from the middle of Y axis
		}
		//actually crop or resize the image.
		imagecopyresampled($dest, $src, 0, 0, $startX, $startY, $destwidth, $destheight, $sampleX, $sampleY);
		// Output and free from memory
		imagejpeg($dest, $theNewImage, 100);
		imagedestroy($dest);
		imagedestroy($src);	
	}
	//set up the new image location and return
	$theNewImage = get_bloginfo('wpurl').'/'.$theNewImage;
	return $theNewImage;
}</pre></td></tr></table></div>

</div>
]]></content:encoded>
			<wfw:commentRss>http://www.blairkelly.ca/2011/11/25/resize-images-with-php/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Get Attachment ID from Source URL</title>
		<link>http://www.blairkelly.ca/2011/11/24/get-attachment-id-from-source-url/</link>
		<comments>http://www.blairkelly.ca/2011/11/24/get-attachment-id-from-source-url/#comments</comments>
		<pubDate>Thu, 24 Nov 2011 14:40:15 +0000</pubDate>
		<dc:creator>blair</dc:creator>
				<category><![CDATA[Blog]]></category>
		<category><![CDATA[WebDev]]></category>
		<category><![CDATA[Wordpress]]></category>
		<category><![CDATA[Attachment]]></category>
		<category><![CDATA[ID]]></category>
		<category><![CDATA[Image]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Source]]></category>

		<guid isPermaLink="false">http://www.blairkelly.ca/?p=2484</guid>
		<description><![CDATA[If you need to get an attachment ID from the source URL, use this function.]]></description>
			<content:encoded><![CDATA[<p>You might find a need in WordPress to get an attachment from its source URL. I use it to ID images, I haven&#8217;t tried it with anything else. Obviously, this only works if you&#8217;ve uploaded the attachment through the WordPress interface.</p>
<div id="codecontainer">

<div class="wp_syntax"><table><tr><td class="line_numbers"><pre>1
2
3
4
5
6
7
</pre></td><td class="code"><pre class="language" style="font-family:monospace;">//gets the attachment id by giving it the source URL of the image or other attachment.
function get_attachment_id_from_src ($attachment_src) {
	global $wpdb;
	$query = &quot;SELECT ID FROM {$wpdb-&gt;posts} WHERE guid='$attachment_src'&quot;;
	$id = $wpdb-&gt;get_var($query);
	return $id;
}</pre></td></tr></table></div>

</div>
]]></content:encoded>
			<wfw:commentRss>http://www.blairkelly.ca/2011/11/24/get-attachment-id-from-source-url/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>The Pro-Life Walking Dead</title>
		<link>http://www.blairkelly.ca/2011/11/23/pro-life-walking-dead/</link>
		<comments>http://www.blairkelly.ca/2011/11/23/pro-life-walking-dead/#comments</comments>
		<pubDate>Wed, 23 Nov 2011 16:30:01 +0000</pubDate>
		<dc:creator>blair</dc:creator>
				<category><![CDATA[Argument]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Popular Media]]></category>
		<category><![CDATA[Science]]></category>
		<category><![CDATA[Social Issues]]></category>

		<guid isPermaLink="false">http://www.blairkelly.ca/?p=2461</guid>
		<description><![CDATA[Some readers of Amanda Marcotte's Slate piece think it's silly to criticise a show about zombies for scientific inaccuracy. Are they right?]]></description>
			<content:encoded><![CDATA[<p>In her Slate piece &#8220;<a href="http://www.slate.com/blogs/xx_factor/2011/11/22/_the_walking_dead_characters_mistakenly_think_the_morning_after_pill_causes_abortion_do_the_writers_.html?wpisrc=slate_river" target="_blank">The Walking Dead Spreads Anti-Choice Misinformation</a>,&#8221; Amanda Marcotte argues that the writers of the TV series <i>The Walking Dead</i> deliberately spread misinformation about birth control in the latest episode, which aired this past Sunday. (In the episode, one of the characters is pregnant and decides to abort her baby. She mistakenly believes morning-after pills will get the job done.) Marcotte says her &#8220;honest impression is that whoever came up with this plot&#8230;mistakenly thinks that morning-after pills are abortion. If they had intended the misinformation to be a comment on the characters&#8217; ignorance, there was no indication of it.&#8221;</p>
<p>Sifting through the comments, I found an interesting objection: one commenter pointed out that a character in the show questions whether the pills would have the desired effect. For me, the jury is still out as to whether that would help make the viewer aware that morning-after pills aren&#8217;t effective at inducing abortions. (I&#8217;m leaning towards probably-not.) One of the less-interesting objections I found in the comments takes issue with the fact that Marcotte is picking on a matter of scientific factuality in a show about <i>zombies</i>. In the words of one of the many commenters making this weak objection: &#8220;perhaps that&#8217;s the reason the writers chose to stay out of the particulars of the science about it[:] It&#8217;s only a show about ZOMBIES, after all. Not a NatGeo documentary.&#8221;</p>
<p>If she had been decrying the ability of a dead guy to walk around and harass people, I&#8217;d say Marcotte was missing one of the show&#8217;s main points. But she is talking about a controversial, real-life issue. We really do hold people to account for what they portray in their fictional works. If an author takes as &#8220;normal,&#8221; or &#8220;everyday,&#8221; something that is morally wrong, we expect an answer for it. Imagine if, in <i>The Walking Dead</i>, all black zombies were <a href="http://en.wikipedia.org/wiki/Lynching_of_Jesse_Washington" title="Warning: Graphic Content. The Lynching of Jesse Washington" target="_blank">hung in trees set and set alight</a>, while all white zombies were quickly dispatched with a shot to the head and buried with reverence. (And all-else being equal: there wasn&#8217;t also some parallel-universe &#8220;race-war&#8221; happening at the time of the outbreak.) If not the cancellation of the series, we would rightly expect somebody to be fired. In this case, dismissal would probably be a bridge too far. But, as one presumably holds authors to account for other issues that affect our society, it is silly and dishonest to cast aspersions on the act of complaining about a point of scientific accuracy, one whose truth has a real effect on the well-being of our citizenry, even in a show about zombies.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.blairkelly.ca/2011/11/23/pro-life-walking-dead/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

