If you haven’t heard of Air Swimmers before, you probably had a miserable life. Air Swimmers are inflatable foil balloons made in the shape of fish. But what makes them really awesome is the fact that they can be remotely controlled to fly around in a room. One of my friends, Claudio, developed an obsession interest for them; and that’s when we decided to create our own hackuarium.

The idea was simple: buying a bunch of Air Swimmers, hacking into their controllers and running a swarm simulation to control them. If you’re interested in doing the same and you have a basic knowledge of electronics …you’re reading the right post. At the end of this article you can find the links to buy all the necessary components.

How it works…

The first step is to understand how the remote controller works. Every controller has an IR sensor and it usually works with a proprietary protocol. We wanted to use a modular approach which enables us to set up new fish with little to no work. For that reason, we decided to sniff the IR packets sent by the remote controller, so that we could then replay them at will.

This technique is often referred as sniffing, since the device we’ll build will detects IR messages which were not originally directed to it. This particular approach works perfectly with the majority of IR remotes and devices, since there is no actual processing involved. Every time you push a button on the remote, you’re sending the same message; the receiver ignores where the message comes from and just executes it.

Step 1: Building the IR receiver

The circuit above uses a an Arduino Uno board connected to an IR receiver (in black). There are several types of IR receivers; for this tutorial is important to use a digital receiver (like the TSOP4838) and not an IR photocell . A digital receiver is sensitive to 38KHz IR signals; if it detects one, it’ll output a low voltage (0 volt), or a high one (5 volt) otherwise. A photocell, instead, acts like a variable resistor and its voltage output linearly mirrors the amount of IR light is has been exposed to. Remotes typically work on digital signals and that’s why we’ll a digital receiver.

The schematics show that I also used an IR emitter. It’s important to couple it with the right resistors; too high and it won’t turn on, too low and it will burn. IR diodes typically requires 180 Ohm resistors. Double check on your datasheets to be sure!

To decode the digital signals that the IR receiver will produce, we’ve been using a library called Arduino-IRremote, which comes with several demos and examples. Once you’ve installed the library, you can test your setup with this code.

#include <IRremote.h> int RECV_PIN = 11; IRrecv irrecv(RECV_PIN); decode_results results; void setup() { Serial.begin(9600); irrecv.enableIRIn(); // Start the receiver } void loop() { if (irrecv.decode(&results)) { Serial.println(results.value, HEX); irrecv.resume(); // Receive the next value } delay(100); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <IRremote.h> int RECV_PIN = 11 ; IRrecv irrecv ( RECV_PIN ) ; decode_results results ; void setup ( ) { Serial . begin ( 9600 ) ; irrecv . enableIRIn ( ) ; // Start the receiver } void loop ( ) { if ( irrecv . decode ( &results)) { Serial.println(results.value, HEX); irrecv . resume ( ) ; // Receive the next value } delay ( 100 ) ; }

The pin referred in line 3 must match the one you have used for your IR receiver. Line 14 decodes the raw data stored into results, either from irrecv.enableIRn() or irrecv.resume(). Once started, the program should display data when exposed to the light or a remote controller.

Step 2: Sniffing the code

So far, the setup is already able to sniff packets. irrecv.decode, in fact, shows these packets in a Human-readable format, But if we need to re-send them, the actual raw data has to be extracted and converted in a format which is compatible with irsend. The following code, inspired by this one, converts the inputs read from the receiver into a list of integer, separated by commas.

// Storage for the raw data unsigned int rawCodes[35]; int codeLen; void loop() { if (irrecv.decode(&results)) { extractRaw(&results); // Displays the raw codes for (int i = 0; i < codeLen; i ++) { Serial.print(rawCodes[i]); Serial.print(", "); } Serial.println(); irrecv.resume(); // Receive the next value } delay(100); } // Extracts the raw data void extractRaw (decode_results *results) { codeLen = results->rawlen - 1; // To store raw codes: // Drop first value (gap) // Convert from ticks to microseconds // Tweak marks shorter, and spaces longer // to cancel out IR receiver distortion for (int i = 1; i <= codeLen; i++) if (i % 2) // Mark rawCodes[i - 1] = results->rawbuf[i]*USECPERTICK - MARK_EXCESS; else // Space rawCodes[i - 1] = results->rawbuf[i]*USECPERTICK + MARK_EXCESS; } 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 // Storage for the raw data unsigned int rawCodes [ 35 ] ; int codeLen ; void loop ( ) { if ( irrecv . decode ( &results)) { extractRaw(&results); // Displays the raw codes for ( int i = 0 ; i < codeLen ; i ++ ) { Serial . print ( rawCodes [ i ] ) ; Serial . print ( ", " ) ; } Serial . println ( ) ; irrecv . resume ( ) ; // Receive the next value } delay ( 100 ) ; } // Extracts the raw data void extractRaw ( decode_results * results ) { codeLen = results - > rawlen - 1 ; // To store raw codes: // Drop first value (gap) // Convert from ticks to microseconds // Tweak marks shorter, and spaces longer // to cancel out IR receiver distortion for ( int i = 1 ; i < = codeLen ; i ++ ) if ( i % 2 ) // Mark rawCodes [ i - 1 ] = results - > rawbuf [ i ] * USECPERTICK - MARK_EXCESS ; else // Space rawCodes [ i - 1 ] = results - > rawbuf [ i ] * USECPERTICK + MARK_EXCESS ; }

If working correctly, it should now display something like this:

1800, 400, 200, 400, 250, 350, 250, 1800, 400, 200, 400, 250, 350, 250, 1800, 400, 200, 400, 250, 350, 250, 1 2 3 1800 , 400 , 200 , 400 , 250 , 350 , 250 , 1800 , 400 , 200 , 400 , 250 , 350 , 250 , 1800 , 400 , 200 , 400 , 250 , 350 , 250 ,

Step 3: Sending the signal back

The numbers printed are the exact values that need to be sent to irsend.sendRaw. The only thing you’ll have to do is to store them into an array.

// Original: "1800, 400, 200, 400, 250, 350, 250," unsigned int code [] = { 1800, 400, 200, 400, 250, 350, 250 }; 1 2 // Original: "1800, 400, 200, 400, 250, 350, 250," unsigned int code [ ] = { 1800 , 400 , 200 , 400 , 250 , 350 , 250 } ;

Air Swimmers have several controls: left, right, dive up and dive down. You have to sniff all of these inputs and save them into different arrays.

It is interesting to notice that some remote controllers are sending the same code, over and over again for a certain period of time. Errors in IR signals are very common, so that might be a technique to ensure that at least one of them reaches the target. If that’s an intended behaviour you want to replicate, you can use the following code.

// Sends a previously sniffer IR signal void sendCmd (unsigned int * code, unsigned long duration) { unsigned long timeStart = millis(); do { irsend.sendRaw(code, codeLen, 38); // 38KHz delay(40); } while (millis() <= timeStart + duration); delay(40); } 1 2 3 4 5 6 7 8 9 10 11 // Sends a previously sniffer IR signal void sendCmd ( unsigned int * code , unsigned long duration ) { unsigned long timeStart = millis ( ) ; do { irsend . sendRaw ( code , codeLen , 38 ) ; // 38KHz delay ( 40 ) ; } while ( millis ( ) < = timeStart + duration ) ; delay ( 40 ) ; }

Line 6 is the one which actually sends the code. irsend is configured to use the pin 3.

What went wrong…

The IR sniffer works amazingly well. Adding a new Air Swimmer to the swarm is relatively easy, but it need to manually detect and configure the IR messages for each signal. We wrote a web interface to monitor and control the fish remotely, and that works as well. The only problem we experienced is the fact that Air Swimmers are incredibly challenging to control. When they are in big space, they’re really impressive. But in a small room the precision you get is too low to have a real swarm simulation. Moving them randomly doesn’t seem different at all. The real issue with them is that they’re very sensitive to air flows and temperature changes. They can suddenly move making virtually impossible to know their position without an external monitoring system. Helium naturally leaks, so after twelve hours you need to fill the balloon again. Oh, and one of the Air Swimmers exploded. I mean, it literally exploded.

Conclusion

This post explained how to hack an unknown remote controller by sniffing its IR packets. For the vast majority of applications, there is no need to understand the protocol used; messages are often independent and have no protection or timestamp. This is perfect to control a TV, a garage door or (like we did) a bunch or Air Swimmers.

The components

Providing that you already have an Arduino, this project can be done with less than $10. If you want to experiment with IR remotes, I advise you to get these 5 pairs Infrared Diode LED IR Emission and Receiver, which comes with all that you need. Alternatively, you can get the TSOP4838 which are qualitatively better. Resistors are incredibly cheap: I have used 180 Ohm ones for this tutorial.























<br /><br />

<img src=”http://wms-eu.amazon-adsystem.com/20070822/GB/img/noscript.gif?tag=alanzucc-21″ alt=”” /><br /><br />



Other resources