Using arbitrary remotes with the Arduino IRremote library

My IRremote library decodes output from many infrared remotes: Sony, NEC, RC5, and RC6. However, many remotes use other protocols. While it's not too hard to add support for other protocols, I wanted to show how the library can be used with arbitrary remote controls.

This post describes a simple routine that will generate a unique code for each key on an arbitrary remote. The demo application flashes the LED the appropriate number of times when I push the 0-9 button on my remote.

How it works

The IRremote library records the duration of each (modulated) pulse sent by the remote control. Each key on the remote corresponds to a particular code value, which is converted to a particular sequence of pulses. If you know the encoding algorithm, you can determine the code value, and thus the key pressed. However, for many applications it doesn't really matter what the original code value is, as long as you can uniquely distinguish each key. Thus, if you can turn each unique sequence of pulses into a unique value, then this value will indicate the desired key.

To do this, I look at the duration of successive pulses. If the pulse is shorter than the previous, I assign 0. If the pulse is the same length, I assign 1. If the pulse is longer, I assign 2. (I compare on-durations with on-durations and off-durations with off-duations.) The result is a sequence of 0's, 1's, and 2's. I hash these values into a 32-bit hash value.

With luck, the 32-bit hash value will be unique for each key. Note that these hash values are arbitrary, and don't have any obvious connection with the underlying code value. Most encoding protocols use two different durations, so comparing shorter vs longer will work, but you can imagine a code with multiple duration values that wouldn't work here. In addition, hash collisions could occur, but are pretty unlikely with a 32-bit hash.

Code

The code can be downloaded from IRhashcode.pde. The sample application prints the "real" decoded value and the hash value. The "real" value will only work for supported protocols (Sony, RC5, RC6, NEC), but the hash value should work for almost any remote control.

The code is pretty straightforward. compare() compares the two measured durations within the 20% tolerance.

int compare(unsigned int oldval, unsigned int newval) {
  if (newval < oldval * .8) {
    return 0;
  } 
  else if (oldval < newval * .8) {
    return 2;
  } 
  else {
    return 1;
  }
}
The actual decoding is fairly simple. results->rawbuf holds the measured durations as space, mark, space, mark, ... The loop compares each duration with the next of the same type, and adds that value to the hash result.
#define FNV_PRIME_32 16777619
#define FNV_BASIS_32 2166136261
unsigned long decodeHash(decode_results *results) {
  unsigned long hash = FNV_BASIS_32;
  for (int i = 1; i+2 < results->rawlen; i++) {
    int value =  compare(results->rawbuf[i], results->rawbuf[i+2]);
    // Add value into the hash
    hash = (hash * FNV_PRIME_32) ^ value;
  }
  return hash;
}
The mystery FNV numbers come from the FNV 32-bit hash function, which combines all the values into a single 32-bit value.

The following example code prints out the "real" decoded value and the hash decoded value to the serial port. You will want to use this code to figure out what hash value is associated with each key on the remote.

void loop() {
  if (irrecv.decode(&results)) {
    Serial.print("'real' decode: ");
    Serial.print(results.value, HEX);
    Serial.print(", hash decode: ");
    Serial.println(decodeHash(&results), HEX);
    irrecv.resume(); // Resume decoding (necessary!)
  }
}
Here's a slightly more realistic example. It receives a code from a Philips remote and flashes the LED appropriately. If you press "1", it flashes once, if you press "8" it flashes 8 times, etc. The code simply uses a case statement to match against the pressed key, and blinks that many times.

You will need to modify this to work with your remote. If it doesn't recognize the code, it will print it to the serial output. Put these unrecognized values into the code to make it work with your remote.

#define LEDPIN 13
void blink() {
  digitalWrite(LEDPIN, HIGH);
  delay(200);
  digitalWrite(LEDPIN, LOW);
  delay(200);
}  

void loop() {
  if (irrecv.decode(&results)) {
    unsigned long hash = decodeHash(&results);
    switch (hash) {
    case 0x322ddc47: // 0 (10)
      blink(); // fallthrough
    case 0xdb78c103: // 9
      blink();
    case 0xab57dd3b: // 8
      blink();
    case 0x715cc13f: // 7
      blink();
    case 0xdc685a5f: // 6
      blink();
    case 0x85b33f1b: // 5
      blink();
    case 0x4ff51b3f: // 4
      blink();
    case 0x15f9ff43: // 3
      blink();
    case 0x2e81ea9b: // 2
      blink();
    case 0x260a8662: // 1
      blink();
      break;
    default:
      Serial.print("Unknown ");
      Serial.println(hash, HEX);    
    }
    irrecv.resume(); // Resume decoding (necessary!)
  }
}

Debugging

The most likely problem is a key will occasionally have different code values. This is likely due to random errors in measuring the pulses. The code considers any durations within +/- 20% to be equal; you can try increasing this value.

If you're using a RC5/RC6 remote, each key will alternate between two different values. In that case, you probably want to use the "real" decoded value, rather than the hashed value, since you can mask out the toggle bit.

If your remote uses a protocol with multiple duration values, you probably won't get unique values from this algorithm.

For other problems, see the IRremote page, which also has a schematic for wiring up the IR detector.

Conclusion

This IR hash algorithm extends the IRremote library to be usable with many more types of infrared remote controls. Please let me know if you use it, and I'll add a link to your project if you want.

19 comments:

FolderBulk said...

Hi ! I want to know if it is a way of extracting raw data from HASH number .
Thank's !

Ken Shirriff said...

No, the hash reduces the data down. If you want the raw data, it's in results->rawbuf, an array of length results->rawlen. The idea of the hash is it's a shortcut instead of processing the raw data yourself.

FolderBulk said...

I need to find a way of compresing / decompresing rawdata so I can save it in EEPROM . Do you have any idea?

Ken Shirriff said...

FolderBulk: the TV-B-Gone efficiently compresses its codes. There's a detailed description at TV-B-Gone Design under "Code Compression". My TV-B-Gone for Arduino uses the same compression.

Anonymous said...

Ken this is marvellous! I am doing a project with a Cypress PsoC and wanted to use an arbitrary remote controller as an input device.For a while i've been lingering with the lirc project and about ways to convert their input device data base to a set of #defines that could be compiled, but this is better - by far better.Now I can make a self learning receiver without even knowing the protocoll!!
It still needs some refinements,such as a solution for the toggle bit in RC5 but also a solution for remotes transmitting only a "repeat" code when a key is held permanently down.
But this is a good base to start with. Thanks!

Peter

Anonymous said...

Hi! You have done really a good job, the IR library for arduino is amazing. I'm trying to implement RCA protocol to decode a Xbox remote but I'm new to programming and it's still too difficult for me but however I'll try. I have problem to modify the .cpp file, I'm not able to write the function to handle the rca protocol, have you plan to implement it in the future in your library? Greetings from Italy and sorry for my ugly english!

chris said...

hi i'm using this sketch:
#include

int RECV_PIN = 11;

IRrecv irrecv(RECV_PIN);
int on = 768928;
decode_results results;

void setup()
{
Serial.begin(9600);
irrecv.enableIRIn(); // Start the receiver
}

void loop() {
if (irrecv.decode(&results)) {
Serial.println(results.value, HEX);
switch (results.value){
case 768928D7 :
digitalWrite(13,HIGH);
break;
}

delay(200);
irrecv.resume(); // Receive the next value
}
}
}

but i cant uploaded.It says:
10: error: invalid suffix "D7" on integer constant

Please Help

Its said...

it only worked for me when i changed the "FNV_PRIME_32" to 2166136261

#define FNV_PRIME_32 2166136261
#define FNV_BASIS_32 2166136261


can you explain why please. i need to understan how the hash works

Vedran Kordić said...

A small correction for this snippet:

if (newval < oldval * .8) {
return 0;
}
else if (oldval < newval * .8) {
return 2;
}

The first expression is true for new values outside the 20% tolerance of the old value, but the second expression is true only for values outside 25% tolerance. You are multiplying different sides of the inequality each time, and (oldval < newval * .8) is equivalent to (newval > oldval * 1.25).

Cheers.

Edwin said...

Better and more understandable than most of the codes I have seen on this. I just have a question. I am using a (universal) remote set to do Philips RC5 codes.
If I press e.g. the 3, the code shown indeed is a '3' (twice) but if I then press it a second time, it will be '803'. Same for the other keys: 1-801, 2-802 etc. The Hashcodes are different though. Not a real problem but I was just wondering why this is?

Edwin said...

Ah, to answer my own question, it is most likely the 'toggle bit' that is set

sitzia.it said...

hello, how can I send a hash code from Arduino with infrared transmitter?

sitzia.it said...

hello, how can I send a hash code from Arduino with infrared transmitter?

Ken Shirriff said...

Hi sitzia. You can't send a hash code, since the hash reduces the data down to a single number. There's not enough information in the hash code to send the original signal.

headphones said...

Hello , I'm searching for a way to handle NEC extended protocol (receiving & sending) . If I understood right it is compossed of 16 bits address and 16 bits command vs 8 bits in standart protocol . Is there a way for decode / send NEC extended with your library ? Thanks

John Wolf said...

To get the loop to repeat after a TV remote command sent, I had to replace the irrecv.resume() method with irrecv.enableIRIn() method. I assume this resets the timers. I noticed in the IRremote source code if a known device is not detected, the code disallows a continuation without modifying the code. I didn't want to do that. I assume this is what was intended, even though not mentioned in your sketch.

Cord Slatton said...

I have decoded my codes for my remote and your IR library is awesome, thanks for taking the time to make things easier on the rest of us!
I am however having trouble using an IR remote with other blocking functions, specifically an RFID reader...each one seems to need to wait all the time in order to be ready for the signal. What I want to be able to do is have a RFID reader running normally but be able to put it into manual mode via the IR remote. IF you need to see my code for the RFID or IR remote let me know, I didn't want to clutter things up if it wasn't needed. Thanks!

Ryan said...

Looks like it's been awhile since this code's gotten some appreciation..so thanks for taking the time to write it and for keeping it up for others to use. I'm just starting out with coding/programming with an Arduino Uno, not much experience at this layer of computing. I picked up a little IR remote from Radio Shack on clearance along with an IR sensor that was meant to be an add-on for a Robotics kit they sell. I just finished running your code and producing hash values for all 8 buttons, and a few 2-button combos. The code was exactly what I needed (the little remote doesn't return any known protocols). Next step will be to come up with a project where I can use the hash values to actually control something, my 9 year old has been tasked with building something so we can control it, most likely will involve LEGO's. THanks again!


/* Hash values this program returned for the Radio Shack Robotics Kit IR Remote>>This is the B-2 bomber V-shaped remote:

* SW NUMBER (probable use/direction): hash values from IRhashdecode
*
* SW1 (up/raise/fwd): 'real' decode: 962814CA, hash decode: 962814CA
* SW2 (left): 'real' decode: B2CC429A, hash decode: B2CC429A
* SW3 (down/lower/rev): 'real' decode: 5990708A, hash decode: 5990708A
* SW4 (right): 'real' decode: B012615A, hash decode: B012615A
* SW5 (forward/extend): 'real' decode: 8D2A4BAF, hash decode: 8D2A4BAF
* SW6 (reverse/retract): 'real' decode: 1C22DE05, hash decode: 1C22DE05
* SW7 (left aux.): 'real' decode: 7A6E10BA, hash decode: 7A6E10BA (one pulse per button-press/momentary)
* SW8 (right aux.): 'real' decode: 97123E8A, hash decode: 97123E8A (one pulse per button-press/momentary)
*
* COMBO's:
* 2+5 (forward left): 'real' decode: 23D3B043, hash decode: 23D3B043
* 4+5 (forward right):'real' decode: 8D0FEBAB, hash decode: 8D0FEBAB
* 2+6 (reverse left): 'real' decode: B2CC4299, hash decode: B2CC4299
* 4+6 (reverse right) 'real' decode: B0126159, hash decode: B0126159
* 5+1 (forward up) 'real' decode: 72F8273, hash decode: 72F8273
* 5+3 (forward down) 'real' decode: CA97DE33, hash decode: CA97DE33
* 6+1 (reverse up) 'real' decode: 962814C9, hash decode: 962814C9
* 6+3 (reverse down) 'real' decode: 59907089, hash decode: 59907089

*
* other 2 button combos are available which return unique hash identifiers. Had enough fun for one night.

*

*
* The actual mark and space timing values produced with the IRrecvDemo were identical in "pattern" to the values given at thh LIRC project website for this particular remote. Actual time values were "off" a bit like Ken had explained they might be.

https://sf.net/p/lirc-remotes/code/ci/master/tree/remotes/radio_shack/kit-1-and-2.conf
*

alexander alzate said...

HELLO, IM TRYING TO BUILD A REMOTE CONTROL FOR MY TV.
IM REALLY NEW ON ARDUINO. I KNOW IT CAN BE DONE WHAT I WANT. BUT I NEED A LOT OF HLEP IF YOU CAN PLEASE.
I GOT A VERY SMALL IR REMOTE CONTROL AND I WANT TO USE IT TO CONTROL MY TV, WHAT I WANT IS TO SAVE ALL THE CODES OF MY SAMSUNG TV INTO ARDUINO AND SAVE THE CODE OF MY SMALL REMOTE CONTROL.
SO IF A PUSH THE POWER BUTTON ON MY SMALL REMOTE THE ARDUINO WILL SEND THE TV CODE.
SOMETHING LIKE THIS, IF IRECV 80BF837C, IRSEND B54A50AF.
I JUST DONT KNOW HOW TO WRITE THE CODE, IF YOU CAN PLEASE HELP ME I REALLY APRECIATE IT.
SO SORRY FOR MY ENGLISH, I SPEACK SPANISH.
THANKS AGAIN.