Hacking the Harmony RF Remote

Why

For my home automation system I’ve been looking for a sleek universal remote. Some of the requirements are:

  • RF (my A/V cabinet is in the garage)
  • Great ergonomics
  • Inexpensive (<$100)
  • Not too many buttons
  • Use regular batteries so I don’t have to put it back in a charging station after use
  • High WAF (Wife Approval Factor)
  • Ability to integrate it to my DIY home automation system

I finally found it, the Logitech Harmony which is part of their Ultimate package ($130+), but you can buy it as an add-on for $30 (http://www.amazon.com/Logitech-Harmony-Companion-Ultimate-915-000245/dp/B00LTKGFDQ). After some research I figured out that it’s using the popular NRF24LE1 chip (NRF24L01+ radio plus a 8051 MCU). That is totally hackable!

My ultimate goal is to attach a cheap NRF24L01+ chip (http://www.amazon.com/nRF24L01-Wireless-Transceiver-Arduino-Compatible/dp/B00E594ZX0) to my home automation system (based on Raspberry Pi) and use this remote to control my A/V equipment.

I figured there are two phases to this goal, phase one is to determine the RF protocol and ids used to send packets between remote and hub. Phase two would be to understand the packet protocol and see if there are any software-based frequency hopping, encryption, etc.

What I know

Remote

  • Nordic Semiconductors NRF24LE1H (http://www.nordicsemi.com/eng/Products/2.4GHz-RF/nRF24LE1)
  • No USB or reset button (so I assume no firmware upgrades)
  • Radio packets around 2.446 GHz, i.e. channel 46 in the NRF24 chip (not confirmed yet)
  • Seems to use the auto-repeat and ack functionality of the chip (Enhanced ShockBurst™)

Hub

NRF24xxx chip

  • 2.4 GHz ISM band operation
  • 250 kbps, 1 Mbps, or 2 Mbps data rate
  • 126 RF channels (2.400 GHz to 2.525 GHz)
  • No promiscuous mode (network id, 3-5 bytes, is stripped from SPI output)
  • GFSK modulation

Approach

As I was researching the remote and trying to find people who have hacked it, or hacked the NRF24 chip its using, I figured there are four approaches to accomplish the first phase in the ultimate goal:

  1. Request the source code from Logitech for their hub since it’s based on Linux (GPL license)
  2. Sniff the data traffic using another NRF24L01 chip
  3. Reverse engineer the raw RF signal
  4. Reverse engineer the firmware in either the remote or the hub

All it (should) take is to find the answer to these parameters:

  • RF channel
  • Data rate (250 kbps, 1 Mbps, 2 Mbps)
  • 3-5 byte network id

Easy Route

I found out that Logitech had published the source code to some of their other Linux-based products at https://opensource.logitech.com, but as of April 14 2015 they had not posted the source for the Harmony Hub there. But now it’s there! Maybe my email to them helped! Here’s the link: https://opensource.logitech.com/opensource/index.php/Logitech_Harmony_Hubs

I haven’t been able to actually find anything useful, I was hoping they would include the driver for the CC2544 chip, but at least they comply with the license so that’s nice.

Brute Force

Since there are only a handful of parameters I figured if I try a couple of settings I may get luck. I tried some of the default/sample settings and also the (limited) promiscuous mode that someone has had success with (http://goodfet.sourceforge.net/clients/goodfetnrf/). The main problem is the 3-5 byte network id, too many combinations. A friend suggested to try all bytes in the remote firmware (a few kB), I haven’t tried that yet, good suggestion. One of the issues is that it takes a button click to generate a packet and I can only handle so many clicks per second. Also it seems that the firmware rate-limits the clicks, even if I bang on a button the amount of (RF) packets getting generates doesn’t increase. I assume it’s waiting to get its packets acknowledged by the hub.

Reverse Engineer RF

I had read about the HackRF One (https://greatscottgadgets.com/hackrf/), a really nice SDR (Software Defined Radio) with 6 GHz bandwidth, 20 M samples/sec. But at $340 it was more than I was willing to spend for this project. I borrowed a TI CC dev kit, but it was the previous generation chipset so it didn’t support 1 Mbps-2 Mbps. Then I found this article: http://blog.cyberexplorer.me/2014/01/sniffing-and-decoding-nrf24l01-and.html, and it was exactly what I was looking for. I ordered the SDR from Amazon and the MMDS down converter from AliExpress. Super-excited when I received it all I managed to get… nothing out of it. My guess is that either the down converter isn’t good enough, I don’t have the parameters for rtl_fm correct (frequency and gain is very sensitive for the fm demodulation to work, or it needs a lot more packets than I can generate from clicking the buttons on the remote. The author is using a wireless mouse to generate the traffic. There is also another article (http://hackaday.com/2014/08/05/sniffing-nrf24l01-traffic-with-wireshark/), but it’s used to decode the traffic once the network id is known.

One issue with using rtl_fm is that there’s no visual indication what’s going on. When I used SDR# (http://sdrsharp.com/) I was able to see the signal from the Harmony remote in the waterfall, but when I tried the same settings in rtl_fm I didn’t get any decoded output. I also learned about GnuRadio (http://gnuradio.org/redmine/projects/gnuradio/wiki), where I again could see the waterfall, but when I tried to create a low-pass filter and FM demodulator, I just got garbage out. I’m also the first to acknowledge that I’m lacking the fundamental skills to operate GnuRadio…

I did manage to record a sample from GnuRadio that include a packet from the remote. I used a tool called Baudline to visualize it and it had an export function that saves a selection of the sample to a wav file. I’ve linked to it here if anyone knows how to decode GFSK from it. It’s about 10 MB and the sample rate is 2M samples/sec (fastest the SDR-dongle could support), and 2 channels (I and Q): https://www.wetransfer.com/downloads/d83525804cd38e26711ac8151e7c4a0320150416000759/12bafc7cff9c31db37e7a1c46e6c814420150416000759/456bf0

Another tool that looks promising is this: http://nutsaboutnets.com/, but again a little pricey.

Reverse Engineer Code

My last approach was to disassemble the firmware from either the remote or the hub. I was surprised to learn that Logitech had left the protection bits turned off on the flash memory of the remote so with a little soldering I was able to hook up a USB-SPI interface and download the flash image from the embedded flash of the NRF24LE1. After several hours of tracing function calls in my favorite disassembler IDA I gave up this approach, too many variables and registers. I found the code that actually sets the network id, rf channel, etc, but the values came from memory variables and I was unable to find the code that populated it. The code may still come handy for phase two though.

Remote with SPI wires attached

Next steps

After my presentation at the Hardware Hackers meeting I got some good tips that I’ll try:

  • Send and capture the NRF24 signal from a known source (NRF24L01+ module attached to Arduino) and compare that to the waveform of the remote. That should confirm the data rate and RF channel.
  • With the RF channel and data rate known I could try all bytes in the flash memory as it’s likely they are stored there (v.s. calculated at start up).
  • See if it’s possible to borrow a newer TI CC dev kit as it has a better promiscuous mode.
  • Capture a better sample using a different SDR (HackRF, etc). Not sure I want to spend the money though.

To be continued…

, ,

  1. #1 by Stephane on March 21, 2016 - 11:07 am

    Hi Hakan,

    I came quite to the same “Why” conclusion, not to forget the above mentioned WAF which is in this area not a detail… Was quite surprised to find someone having same concern…

    Walking around www I found this.

    Sniffing the frames seems to be possible. Being not a radio specialist, I haven’t tried to follow the described process. I understand this is not unreachable for electronics enthusiasts whereby not direct…

    Anyway did you go further in your investigations?

    Regards,

    Stephane

    • #2 by hakanl on March 21, 2016 - 11:10 am

      I never managed to decipher the traffic (it goes into an encrypted channel hopping protocol once it’s paired so makes it very complicated), but I learned that the Harmony remote emulates a keyboard with a Logitech Unifying receiver, so I’m going that route instead.

  2. #3 by Stephane on August 15, 2016 - 2:25 am

    Hi Hakan again,

    Thanks to your indication I tried to pair the Harmony Remote with the Logitech Unifying Receiver, on a regular Windows PC (with the Logitech Unifying SW).
    It works!

    Then I leverage on the great work of Julien Danjou to sniff the exchanged data, on each button press. The code is given above. I run it on a Raspberry Pi under Linux Ubuntu.
    It works also!

    So that you’re able to receive Harmony Remote key press on any computer (should work on Windows too but I didn’t try it out).

    Hope it helps in your hobbies ;-).

    Regards,
    S.

    //============================================================================
    // Name        : HarmonyInput.cpp
    // Author      : Watea
    // Version     :
    //============================================================================
    
    #include <linux/hid.h>
    #include <libusb-1.0/libusb.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <iostream>
    
    // HARMONY USB interface, thanks to USB sniffer
    #define HARMONY_CONFIG_INDEX		0
    #define HARMONY_INTERFACE_INDEX		2
    #define HARMONY_ALT_SETTING_INDEX	0
    #define HARMONY_ENDPOINT_INDEX		0
    #define HARMONY_VENDOR_ID			0x46d
    #define HARMONY_PRODUCT_ID			0xc52b
    #define HARMONY_TIMEOUT				10000
    #define HARMONY_MAX_MSG_LENGTH		100
    #define HARMONY_KEY_POSITION		3 // Found by sniffing
    
    void outputFrame(unsigned char* _frame, int _size) {
    	std::cout << std::hex;
    	std::cout << "Frame => ";
    	for (int i = 0; i < _size; i++)
    		std::cout << (unsigned short) _frame[i] << "/";
    	std::cout << std::endl;
    	std::cout << std::dec;
    }
    void outputKey(unsigned char* _frame, int _size) {
    	std::cout << std::hex;
    	std::cout << "KEY => " << (unsigned short) (_frame[HARMONY_KEY_POSITION] << 8) + _frame[HARMONY_KEY_POSITION + 1] << std::endl;
    	std::cout << std::dec;
    }
    
    int main() {
    	libusb_context *ctx;
    	struct libusb_device_descriptor desc;
    	struct libusb_config_descriptor *config;
    
    	std::cout << "Starting keys sniffer..." << std::endl;
    
    	libusb_init(&ctx);
    	libusb_set_debug(ctx, LIBUSB_LOG_LEVEL_WARNING);
    
    	// Look at the keyboard based on vendor and device id
    	libusb_device_handle *device_handle = libusb_open_device_with_vid_pid(ctx, HARMONY_VENDOR_ID, HARMONY_PRODUCT_ID);
    
    	std::cout << std::hex;
    	std::cout << "Found Harmony Device: " << device_handle << std::endl << std::endl;
    
    	// Get interface
    	libusb_device *device = libusb_get_device(device_handle);
    	libusb_get_device_descriptor(device, &desc);
    	libusb_get_config_descriptor(device, HARMONY_CONFIG_INDEX, &config);
    	const struct libusb_interface *iface = &config->interface[HARMONY_INTERFACE_INDEX];
    	const struct libusb_interface_descriptor *iface_desc = &iface->altsetting[HARMONY_ALT_SETTING_INDEX];
    
    	// Detach & claim interface from kernel driver
    	libusb_detach_kernel_driver(device_handle, HARMONY_INTERFACE_INDEX);
    	libusb_claim_interface(device_handle, HARMONY_INTERFACE_INDEX);
    
    	// Bench for reading HARMONY Keys (Button Press)
    	unsigned char data[HARMONY_MAX_MSG_LENGTH];
    	int actual_length = 0;
    	int index = 0;
    	char next = 'y';
    
    	do {
    		if (next == 'y')
    			std::cout << "Press a Key" << std::endl;
    
    		libusb_interrupt_transfer(
    			device_handle,
    			iface_desc->endpoint[HARMONY_ENDPOINT_INDEX].bEndpointAddress,
    			data,
    			sizeof(data),
    			&actual_length,
    			HARMONY_TIMEOUT);
    
    		// Key Value received (Key Release ignored here)
    		// Note: HARMONY REMOTE seems to send over data; some false positive happen, to ignore in this bench
    		if ((actual_length > 0) &&
    			(data[HARMONY_KEY_POSITION] + data[HARMONY_KEY_POSITION + 1] != 0)) {
    			std::cout << std::endl;
    			std::cout << "Index:" << index++ << std::endl;
    			std::cout << "Length:" << actual_length << std::endl;
    			// Key Frame
    			outputFrame(data, actual_length);
    			// Button value
    			outputKey(data, actual_length);
    			std::cout << std::endl;
    			// Continue?
    			std::cout << "Next y or ~y?" << std::endl;
    			std::cin >> next;
    		}
    		// Not a Key Value Frame
    		else
    			next = 'c'; // Continue
    	} while ((next == 'y') || (next == 'c'));
    
    	// Leave a clean environment
    	libusb_release_interface(device_handle, HARMONY_INTERFACE_INDEX);
    	libusb_attach_kernel_driver(device_handle, HARMONY_INTERFACE_INDEX);
    	libusb_close(device_handle);
    	libusb_exit(ctx);
    
    	return 0;
    }
    
  3. #4 by hakanl on August 15, 2016 - 10:10 am

    Very nice, thanks!

  4. #5 by FTZ on April 1, 2019 - 10:28 pm

    hi Stehane & Hakanl,

    thanks for your great work!
    Its working for me also very well, but one question, did number and arrow buttons also work?
    I get a key for every single button press, except number buttons and the centered ok button ?

    thx
    FTZ

    • #6 by hakanl on April 2, 2019 - 5:54 am

      Thanks! The remote works great for me, I get a key press for every button on it.

      • #7 by FTZ on April 2, 2019 - 6:54 am

        Hi,
        thanks for your quick reply.
        Strange, i tried it with a Logitech unify receiver from a K400 Keyboard paired with a harmony smart control and harmony campanion remote?!?
        How do you pair your devices? Logitech unify pairing tool win/mac?

        best regards!

        FTZ

  5. #8 by hakanl on April 2, 2019 - 7:06 am

    It was a while ago since I did it, but I think I used the pairing tool on Windows.

  6. #9 by FTZ on April 2, 2019 - 8:01 am

    … to strange. I repaired the remote with Windos and Linux tool. The remote was found and marked as Harmony 20+. All buttons are working except still the numbers and arrow buttons ?!?
    Maybe its the receiver, “logi” is printed on it, instead of “logitech” but it has the orang unify symbol…

  7. #10 by hakanl on April 2, 2019 - 9:30 am

    Strange, it seems it would be something with the computer, maybe try it somewhere else? Maybe those key values are captured elsewhere?

    • #11 by FTZ on April 2, 2019 - 12:41 pm

      good point, i will investigate also in this direction. I’m testing on a rasbperry pi running stretch lite with a node red server….thats the only linux pc i have…

      • #12 by FTZ on April 5, 2019 - 11:58 am

        hm, sadly no progress. i ordered a second unify receiver (M/Nc-U00012 and M/Nc-U0008) but the same result, all buttons are working ececpt the numbers and cursor on both remote smart add on and campanion 😦
        I also installed a USB Sniffer on a Win10 to see any traffic, but there is nothing when i press a number button…
        What hardware did you use?

  8. #13 by hakanl on April 7, 2019 - 1:12 pm

    Here’s what it says on mine: LZ421AV-DJ
    M/N:C-U0007

    • #14 by FTZ on April 8, 2019 - 6:27 am

      Thanks for the information! You run it on a raspberry?

  9. #15 by hakanl on April 8, 2019 - 7:44 am

    Indirectly, I have an Arduino with a USB host board connected to a RPi running Windows 10 IoT. The volume up/down comes over as multimedia keys, which is not possible to capture in Windows 10, so I had to do this hack.

    • #16 by FTZ on April 8, 2019 - 7:54 am

      oh, i understand. could you share your arduino code to? i should have a usb host shield some where 🤔

  10. #17 by hakanl on April 8, 2019 - 8:15 am

    Sure, email sent!

    • #18 by FTZ on April 9, 2019 - 12:27 am

      THX!!!

      • #19 by FTZ on April 15, 2019 - 3:04 am

        Got it!! With M/N:C-U0007 Receiver everything is working out of the box! THX!!!!

  11. #20 by hakanl on April 16, 2019 - 6:58 am

    Ah, sweet! Didn’t know it would be limited to that, but great that you got it working!

  12. #21 by DDRBoxman on September 14, 2019 - 5:08 pm

    Would you mind publishing that arduino code? I’d like to play around with it. I’m working on a DIY harmony hub replacement based on arduino.

  13. #23 by Stephane on September 21, 2019 - 7:30 am

    THX, well done!

  14. #24 by Wilfred on July 28, 2020 - 8:25 am

    Hello, all is going nicely here and ofcourse every one is sharing facts, that’s in fact good, keep up writing.|

Leave a reply to FTZ Cancel reply