NRF24L01+ hack on Hunter Douglas shades

I have made pretty good progress in hacking the transmission packets that are sent from my Hunter Douglas Platinum remote control to my window shades.

I piggy-backed my nrf24L01 module SPI bus to the SPI connection on the remote (MISO left open)
and transmitted some data. Then kept my module powered-up, and switched the SPI connection back to the Arduino.

I have determined the following using print details.

RX_ADDR_P0-1 = 0xe7e7e7e7e7 0xc2c2c2c2c2
RX_ADDR_P2-5 = 0xc3 0xc4 0xc5 0xc6
TX_ADDR = 0xe7e7e7e7e7
RX_PW_P0-6 = 0x05 0x00 0x00 0x05 0x00 0x00
EN_AA = 0x00
EN_RXADDR = 0x0b
RF_CH = 0x21
RF_SETUP = 0x0c
CONFIG = 0x0c
DYNPD/FEATURE = 0x00 0x00
Data Rate = 2MBPS
Model = nRF24L01+
CRC Length = 16 bits
PA Power = PA_HIGH
Set_up AW = 3 bytes

I set up a monitor routine to capture the packets and found these packets were used.

G1 up = 3 193 32 1 161
G1 dn = 3 193 32 1 162
G2 up = 3 193 32 2 161
G2 dn = 3 193 32 2 162
G3 up = 3 193 32 4 161
G3 dn = 3 193 32 4 162
G4 up = 3 193 32 8 161
G4 dn = 3 193 32 8 162

All up = 3 193 32 255 161
All dn = 3 193 32 255 162

Here is how the remote works.

As long as I press the up button, 3 193 32 1 161 is sent repeatedly and the shade starts to move as soon as the button is pressed.
When I release the button, 3 193 32 1 33 gets sent (about 60 packets)

I have a receive program to capture what is sent by the remote.

Count = 0 Available = True
Received Data : 3 193 32 1 162
Received Data : 3 193 32 1 162
Received Data : 3 193 32 1 162
Received Data : 3 193 32 1 162
Count = 21 <–21 of the above packets
Received Data : 3 193 32 1 34
Received Data : 3 193 32 1 34
Count = 18 <–18 of the above packets

The shade moves immediately when the remote is pressed.
By the way, the remote sends data on three different channels…I assume different shades use different channels.
I also monitor if “available” drops…and it does not drop unless I have extra delays between radio.write

I programmed the Arduino to send the same exact pattern…my monitor confirms that pattern is the same.

Here is the problem…sometimes the shades respond to the Arduino packets…but most of the time they do not. The scan program output seems to show they are identical packet bursts.

QUESTION FOR THE EXPERTS: What can I check, or what might be different to make this behave like this?

Please follow the advice on posting a programming question given in Read this before posting a programming question

In particular note the advice to Auto format code in the IDE and to use code tags when posting code here

I piggy-backed my nrf24L01 module SPI bus to the SPI connection on the remote (MISO left open)
and transmitted some data. Then kept my module powered-up, and switched the SPI connection back to the Arduino.

I can't follow that. Please make a drawing to illustrate what you are doing and post a photo of the drawing. See this Simple Image Posting Guide

I have made pretty good progress in hacking the transmission packets that are sent from my Hunter Douglas Platinum remote control to my window shades.

Does that mean that your window shades is using an nRF24L01+ for its wireless communication?


is this IR control or bluetooth or something else? I really don't know from your post

Thanks for getting back, I will try to answer the previous questions.

Yes, the remote uses a NRF24L01+ I thought this would be easy.

When I say "Piggyback", I mean I soldered wires to the SPI pins of the remote (SCK,CSN, MOSI).
I powered up my Arduino - NRF module, and then moved the 3 SPI wires for my NRF module to conect to the SPI remote wires.

Now when the remote writes to the local NRF chip, it also writes to my NRF module.
After activating the remote, I just moved the wires back to my arduino, and did a register dump to see what settings the remote was using.

I did not post C++ code, because this is not a coding is more of a "How do I debug this" problem.

It is not a "pipe specific" problem....becasue My scan monitor program confirms the remote only xmits on pipe_0

The remote DOES trans on three channels, and I have modified my code to transmit 20 commands on each channel.

The monitor program will indicate when "available" is dropped. The remote never makes available drop.
And by setting different delays in my code between writes, I can prevent available from dropping.

Bottom line I see (from my limited knowledge) is the monitor program shows the same data packets from my code as from the remote.

I am trying to ask....what would be things to check or consider?
If I loop my code, the shades will work randomly..maybe every 5 - 10 passes

One possibility is that the remote control is not blindly sending a character stream to the shades but constructs the data stream based on some acknowledgement that it receives back from the shades.
The NRF24L01 is a transceiver unit (2 way)
I had a quick look at some instructions I found online for these shades and the device pairing function implies the use of a 2 way information exchange. Since the range of these devices can be quite long, there may be some mechanism to prevent your remote control operating the shades of a neighboring apartment.
That it works every 5 or so times could also imply some timing problem.
You may have to “sniff” the entire data stream between the main chip and the NRF24L01 of the remote unit to find out more.

Here is how the remote works.
It is a combo I/R and RF.
When using I/R, there is no “pairing”…you just point the remote at a shade, and push the up/down button.

When using RF, there are 4 “Group” buttons, and a “ALL” button.
The RF code summary is shown below:

I/R is used to “pair” a shade with a group. NOTE: I can pair multiple shades to a single group button.

G1 up = 3 193 32 1 161 ( 003 193 = controller ID, 32 = fixed val 1= group 161 = up cmd

cmd summary:
G1 up = 3 193 32 1 161
G1 dn = 3 193 32 1 162
G2 up = 3 193 32 2 161
G2 dn = 3 193 32 2 162
G3 up = 3 193 32 4 161
G3 dn = 3 193 32 4 162
G4 up = 3 193 32 8 161
G4 dn = 3 193 32 8 162

all up = 3 193 32 255 161
All dn = 3 193 32 255 162

Here is more detail that I found. This is tricky to explain…note that the first command is repeated as long as the down button is pressed. This means (3 193 32 1 161) is repeatedly sent as long as the button is pressed.
AND the shade will start moving almost immediately after the button is pressed…while the stream of 3 193 32 1 161 is still being transmitted. This tell me the second value packet is not needed to make the shade move.

Then, as soon as the button is released a "fixed "number of packets are sent…but the shade is already moving by now. So I am still not sure what the purpose of the second command is.

This is my code for up & down commands:

byte down_text1{03, 193, 32, 001, 162}; //162 = down 161 = up
byte down_text2{03, 193, 32, 001, 34} ;

byte up_text1{03, 193, 32, 001, 161}; //162 = down 161 = up
byte up_text2{03, 193, 32, 001, 33} ;

This is what the remote control sends (attached file)…I tried to push the button as quickly as possible (min switch closed time).

My Arduino has the exact same printout from the monitor code…“Available” is never dropped.

I need you guys to give me another perspective on approaching this…Thanks…Dan

HD_Remote.txt (5.15 KB)

The existing remote unit probably transmits simultaneously on multiple channels as a precaution against some local interference blocking one or more channels.

I guess the data burst after the button is released is to support a mode where the shades move only during the time a button is pressed, moving until a explicit "stop" signal is received, maybe to prevent jitter in the shade movement if the signal quality is poor.

The odd thing is that it works about 1 time out of 5 to 10 times. That indicates possible timing issue as I said before. There could also be some special phasing between the channels. For example, a transmission of a packet on channel X is followed n milliseconds later by a transmission on channel Y. May be even some sort of challenge/response protocol between the devices where your hard coded packet gives an acceptable response only occasionally.

Are you able to adapt your scanner code so it sees the entire bi-directional data stream between the remote control chip and its NRF24L01, or do you have access to a logic analyser which can watch this traffic ?

If you connect your NRF24L01 to the remote control and disable the inbuilt one, does it all perform correctly ?

Can you set your Arduino / NRF24L01 up as a receiver so it can verify what it receives when the remote control is operated ?.

Incidentally, it doesn't make any difference in the cases here, but preceding a number with a leading zero causes it to be interpreted as an octal number:

byte down_text2[]{03, 193, 32, 001, 34} ;

I still don't understand why you seem to be connecting and disconnecting SPI wires. SPI is a bus and it is normal for it to have several devices connected at the same time.

It seems to me it would be much easier to emulate the IR remote control system.


Get one of the cheap logic analyzers and analyze the setup and any other interaction with the chip.

Like this one.


When you have the basic parameters (addrLen, pipe address, CRC type, packet length, usage of shockburst),
you can listen to the communication without any wires (if still needed).


6VGT - Since I can have more than one shade in a group, I think that means there is no ACK because they all see the same address, and the ACK's would collide. I will try adding delay between the channel number change.
However, I would think that the receiver in the shade is not going to "channel hop".....I assume it would stay on one channel for a command. The remote uses SMD devices, so it is very difficult to connect wires...not sure I can disable the remote-NRF without PCB damage.

But I will pursue a SPI analyzer to capture the SPI command stream...that was a good suggestion to try.

I have a second UNO/NRF running the monitor I can always watch the data streams.

I don't see how ACK packets should make any difference......if the packet stream from the my code matches the remote, I would have thought it would work.

Whandall - great suggestion on this analyzer...I will get one ordered.

Robin2 - I only did the jumper connection one time....this lets the remote set up the registers in my module...then I leave the power on, and move the SPI connection back to the UNO...then dump the registers.
This is how I got 99% of the register settings so I could get the monitor program working.

Got the analyzer today.
I think my register set-up is fine (correct settings).
I think the problem is timing on the radio transmits (CE=1).

The remote loads the payload register with 5 bites, then
the remote generates a CE that is 13us wide. Does not check for any ack or just does a blind transmit.
Then it takes 193us to load the next 5 byte into the payload and change the channel before setting CE high again for 13US.

The remote cycles through three different channels sending one 5byte transfer on each channel..the cycle repeats around 30 times.

My arduino code generates a 212us wide CE, and the fastest that I can transmit the next payload is 319us.

I think the logical thing to try, is to bypass the NRF library, and use the SPI directly to load the 5 byte payload and then raise CE for 13us. I hope the Arduino is fast enough for this.

The remote control uses a PIC16F913 micro-controller.
The logic analyzer was the ticket....Thanks

That was quick work.
The slow speed could depend on how you are using the library. For example it could be retrying a transmission, timeout waiting for an acknowledgement etc.
There is also a fastwrite command which could help.
Post the code you’ve tried for some suggestions.

I tried the fastwrite, but it leaves CE active, so all you have to do is load the buffer.
Plus the remote sends one packet on each channel…and then recycles again though all three channels.
It appears the receiver is sensitive to packet timing.

Will keep you posted…

Here is the latest....and there is still something that I don't understand. Let me recap how the controller works in RF mode.
To move the shade, you push a "Group#" button. I found that this powers up the PCB and initializes the NRF24L01.
Zero retries, 3 byte address, 2 byte CRC.
When you push the up or down button it sends a 5 byte command that includes the group# and the up/down info.
As long as you hold the button, the same command is transmitted on three channels (one cmd, ch change, same cmd, ch chg, etc).
I checked the SPI bus, and it never sees FIFO full, and never does anything buy load the pay_load reg, set channel number, then pulse CE to transmit.
My scanner program sees these commands (I monitor one channel), and the receiver never drops "available".

So I hard coded SPI commands to do the same initialize and bypassed the NRF library.
It now works 95% of the time with my arduino code. I blast out 200 commands on each channel the the shade works. is a strange thing I see.
If I send 3 or less commands on one channel, my scanner sees all three packets.
As I increase the number of packets sent, I start to see a packet count that does not match what I sent.

So I guess my first question: isit reasonable to have two NRF24's about 18" apart and still get lost packets?

I guess it is possible the remote is losing packets also...but it just sends a large number and enough will get through.

Let’s see how you have coded the scanner.

I also have been trying to talk to my Hunter Douglas shades without the greatest success and ended up here on a search. I didn't want to disassemble anything though. Reading through your posts, I noticed few things.

You mention a 5 byte "payload" but what I see is that you have a 3 byte shade Address and a two byte payload. The sent packet would likely look like: 1 byte preamble + 3 byte shade address + two byte payload command + perhaps a CRC. Loading it that way in the RF24 library might fix your timing issues.

Perhaps it also might be helpful sniffing what is sent by the remote vs. what is sent by your hack. The undocumented promiscuous mode of the nrf24L01 might help. It is described in "GitHub - Yveaux/NRF24_Sniffer: Sniffer for Nordic NRF24L01+ modules with MySensors support" and his associated blog posts. A practical use of this mode in sniffing keyboards is at "Promiscuous Wireless Packet Sniffer Project - Black Hills Information Security".

If I get this working (I'm mainly hung up on getting the address without disassembly), I will be happy to post code when I get it working.

I got a hackrf one in the mail just now. I sniffed the pebble controller, demodulated FSK and got this image of two packets. It suggests a standard packet. The one byte 01010101 timing sequence lead in is clear at the beginning of each packet. I get different numbers reading from this sequence than from your example with the pebble.

OK, I went at this the other direction - sniffing rather than disassembly. My Hunter Douglas remote must be different than yours.

My remote's frequency was 2.407 GHz (radio.setChannel(0x07)). It takes 160uS between chirps of which it sends several hundred per button press. Unlike yours, mine sends at 1 MBPS instead of 2 MBPS. My remote sends 1 byte timing, 3 byte address, and 25 byte payload. Like yours, only a few bytes change for all up, all down, all stop.

I did the sniffing at 10.0M with a HackRF I got yesterday using the Windows version of Universal Radio Hacker by Johannes Pohl (a great tool I just tried today). I found that when using the RF24 library, the address bytes need to be entered backward, byte address={0xc0, 0x11, 0x11}; radio.openWritingPipe(address) is transmitted as 1111c0. Similar to your experience, using the RF24 library, the fastest I can get the arduino to go between chirps is 280uS if I have to load the 25 byte payload every time.

Attached is what my decoding of the byte sequences looks like for "all down"-two examples, "all stop", and "all up". Very little changes.

Also, the RF24 library is fast enough when using radio.writeFast. It is not if the transmitter powers down or goes into receive mode looking for an ACK. The top line is sniffed from an Arduino running code I just wrote (62uS blue gap)and the lower line is from the original remote (147uS blue gap). The Arduino can go faster between packets. (Only problem is the RF24 library suggests resting the transmitter 4 ms between each packet.)

Here's my final bit about these Hunter Douglas things. It appears various hardware is used on them. My pebble remote controller did not have a nrf24L01+ inside --which is why I've been beating my head against the wall this afternoon trying to get the timing just right with only a 32 byte register and a tracing that starts high with carrier on.

It appears my pebble remote has a nRF51422 chip instead - when I took it apart. To do this right with the arduino you would need a N51 development board from Nordic - which is about the same price as the pebble controller. It's probably faster just to solder some wires to a used controller.