This is the first in a (probably) three part series.
Checkout the code on BitBucket: https://bitbucket.org/MostThingsWeb/temper-bridge/src/master/main/
About a year ago, I decided to upgrade my old spring mattress to a TEMPUR-Contour Elite Breeze and I cannot say enough good things about it. I opted to also get an adjustable base (this one) for it, which has also been great. It comes with a remote like the one pictured here:
Another advertised feature of the base is control via Android and iPhone apps. Unfortunately, the Android app hasn’t been updated since 2014. I could not get it to work with my Google Pixel, and based on the reviews, I’m not the only one. Clearly, this is DIY territory.
- Build a gateway that communicates with the base using its own RF protocol
- Develop an Android app that can control the base through the gateway (next time)
Other projects that achieve something similar fall into two categories:
- Talking to the onboard WiFi module (e.g. https://github.com/docwho2/java-alexa-tempurpedic-skill)
- Integrating relays into an existing remote control to simulate button presses (e.g. http://www.quadomated.com/technology/automation/diy-tempurpedic-ergo-adjustable-bed-automation/, https://github.com/tomchapin/tempurpedic-remote-relay)
The benefits of reversing the actual RF protocol are (a) no dependence on integrated WiFi and (b) non-destructivity.
Researching the FCC filing
Since the remote is a wireless device, it is regulated by the FCC. We can lookup the FCC ID (UNQTPTAES) to learn some useful information about the device. Confidentially requests by the manufacturer prevent us from accessing the BOM, schematic, and block diagram :/. In this case the most helpful document is the Test Report.
Some interesting excerpts from the test report follow.
One page 7, we learn there is some kind of mapping between the channel number and the frequency. The remote supports lots of channels, which could be useful for people with more than one adjustable base.
Page 12 gives us a glimpse at the signals we will soon be hunting:
Page 13-14 detail that the remote transmits in pulses, e.g. on each key press. This implies that there is no persistent connection between the base and the remote, and that communication is probably unidirectional (remote => base). Makes sense because the base shouldn’t need to talk back to the remote.
Initial exploration with software defined radio (SDR)
In addition to being practical(-ish), I intended to use this project as an opportunity to play with SDRs for the first time. That being said, I will not try to attempt to teach any of the concepts here.
My weapon of choice was the HackRF One (Amazon link). Using the test report as a guide, it was relatively simple to find a signal after playing with the sliders in SDR#:
The test report signal plot is overlaid on the right. We can see that the two signals are very close, so we’re on the right track.
It turns out that the type of modulation in use here is Gaussian frequency shift keying. A big thanks to my friend Tim for identifying it as FSK.
Listening in on the SPI bus
I got relatively far using GNU Radio to demodulate the signal (something I won’t go into here, mainly due to my lack of ability to speak on the subject). Eventually I got bored of it, and sought a faster way to reverse engineer the signal.
For this, I broke out my trusty Saleae logic analyzer (mine is an older model; here’s the newer kind on Amazon). Cracking open the remote, one finds a Si4431 RF transceiver:
And also a Renesas microcontroller:
It is tempting to try and dump the uC’s ROM and reverse the firmware, but the FCC test report from earlier reveals that ROM protection is active. Maybe next time we’ll try anyway, since PIN bypasses like this exist: https://github.com/q3k/m16c-interface.
But for now, the plan is to simply listen in on the SPI bus connecting the uC and RF transceiver. This bus is used for configuring the RF chip and for transmitting the data. The Si443x’s datasheet reveals an incredible number of configurable settings: https://www.silabs.com/documents/public/data-sheets/Si4430-31-32.pdf.
We will capture the traffic on the bus during initialization (i.e. right after installing the batteries) to learn how the uC configures the transceiver. This will reveal the modulation scheme (spoiler: GFSK) and any other parameters we need to impersonate a remote control. To make this easier, I wrote a small Python script that interprets the SPI traffic (exported from Saleae logic as CSV) and displays it as register reads and writes:
This traffic reveals the following details of the modulation scheme. Note that in this case, my remote was set to channel 9345, which is how it came delivered.
- Nominal carrier frequency: 434.5856250 MHz (again, this is dependent on the channel)
- Data rate: 12.8 kbps
- Frequency deviation: 25 kHz (corresponds to 50 kHz bandwidth)
Nominal carrier frequency is another term for the center frequency. Therefore, channel 9345 occupies ~[434.585 MHz – 25 kHz, 434.585 MHz + 25 kHz].
Figuring out the channel mapping
So how does the remote get 434.5856250 MHz from “channel 9345”? This is a question I wrestled with for about 2 days. There is no easy linear relationship. I collected a few dozen initialization sequences, each time with a different (random) channel number and looked for a relationship.
Without further ado, the final answer is the following piecewise function:
It’s not nearly as bad as it looks. In fact, the only part I had to figure out was the piecewise portion:
That portion highlighted in red corresponds to the fc (frequency center) register in the Si443x. So, when the user changes the RF channel, all the uC has to do to tune the transceiver is change the fc register.
The full formula I have presented above was derived from this equation in the datasheet:
Aside: Si443x frequency hopping
The Si443x has a neat feature that lets you do channel-tuning from a single channel select register. You first set the nominal carrier frequency, then the channel step size (increments of >= 10 kHz). It’s generally used for timing critical applications like frequency hopping. So why didn’t the Tempurpedic remote use it? Since the system supports 9999 channels, the 10 kHz channel step is way too big. The remote’s channel scheme uses ~156.2 Hz channel step.
Si443x packet structure
Recall that we’re dealing with burst communications. This means that the receiver will always need to be ready and listening for data. How can a device like this remote control do so in a power-efficient manner? Answer: Combination of preamble (to “wake up” the receiver) and a sync word.
The preamble is designed to be both “easy” to detect and something that is unlikely to be received randomly. A common choice is an alternating series of 1 and 0. The Tempur-Pedic remote uses this sequence, 40 digits long.
The preamble detector may wake up the transceiver in the middle of the preamble. How can it know where the data actually begins? This is what the sync word is for. After detecting the preamble, the receiver searches for sync word for a (configurable) period of time.
The Si443x can operate in fixed or variable length mode. In our case, the remote uses variable length mode, even though all of the data I’ve seen come from it is the same length. So, we’ll need to handle the packet length header.
Data and CRC
The CRC is a sanity check to detect transceiver errors. It’s handled transparently by the Si443x.
Into the data
It’s finally time to investigate what data is actually sent by the remote. Here’s an excerpt of the table I used to decode the protocol. I tuned my remote to various channels and pressed each button.
Immediately, we can see that, independent of channel and command, each transmissions begins with 0x96:
The next three bytes are independent of channel, dependent on command.
The next two bytes are the channel number. E.g. 0x03F2 == 101010
But what about the last part? There is no obvious correlation between neither the command nor channel.
Reverse engineering CRCs
Turns out, the last byte in each transmission is another CRC. But which one? Well, none of the common ones. Thankfully, there are tools for reversing CRCs. I chose this one: http://reveng.sourceforge.net/
All you need to do is feed it a map of inputs (data) => outputs (CRC) and it will brute force the CRC parameters. The output I got is pictured below (the actual command got truncated):
Parameters: width=8, poly=0x8D, init=0xFF, refin=false, refout=false, xorout=0x00, check=0xFD, residue=0x00, name=(none)
The “name=(none)” part means that this set of parameters doesn’t correspond to any well-known CRC.
Recall that the Si443x already has its own CRC. Why does the remote use another one? My best guess is that the engineers chose to add this application-level CRC to guard against data corruption between uC and transceiver. For example, suppose some noise on the SPI bus caused the transceiver to “see” different data that what the uC intended to transmit. The transceiver has no way of knowing this, and so it will dutifully transmit it. The receiver in the base may or may not recognize the corrupted command. An application-level CRC ensures transmission errors on the SPI bus can be detected.
Most of the buttons on the remote don’t seem to be debounced. For example, when pressing a command like STOP or FLAT, the remote will actually transmit it between one to three times (in my experience). This is a good thing, since it increases the probability that the receiver actually receives the command. Those types of commands are idempotent, so even if the base does receive multiple of the same, it doesn’t matter.
Buttons that are meant to raise/lower the legs and head are intended to be held down, which causes the respective command to be repeatably transmitted until the button is released.
The massage buttons are an interesting case. These six buttons increase/decrease the massage intensity of the legs, lumbar, and head region. Initially I had expected that they would be implemented using one command per button, just as the leg/head raise/lower buttons. But in fact, it is more complicated.
There are eleven massage levels, between 0 and 10, with 0 being off. The remote control keeps track of the current massage level for each region. When the user hits the “+” or “-” button, the remote transmits a massage command that encodes the region and the new level. In other words, it transmits the absolute massage level. This means it actually 33 discrete commands to implement the remote’s massage buttons (11 levels * 3 regions). Contrast this to the leg/head raise/lower buttons, which merely transmit relative positioning commands (either “+1” or “-1”).
So why does the massage feature have this complication? Here’s my theory. From the previous section “Command bursting”, we understand that the remote transmits commands multiple times to improve the probability that they are received correctly. This is fine for idempotent commands like STOP, FLAT, etc. It’s also fine for buttons that raise/lower the heads/legs since their positioning is so granular that the user probably won’t notice the difference between a button press that raises their legs 1 step or 3 steps. But since the massage only has 11 levels, the user would definitely notice if one press of “Lumbar message increase” added +1 to the level whereas the next added +3.
A mildly interesting feature of the system is the ability to automatically choose a channel. To do this, you press and hold the FLAT and STOP buttons for ten seconds, then press the STOP button. The remote’s LCD will blink while displaying the new RF channel. While it is blinking, you unplug and then replug the base. If done properly, a relay in the base will click to confirm the new channel has been learned.
While the remote is blinking the new channel, what it’s actually doing internally is transmitting the channel number on a special broadcast channel 5568 (fc = 25088, frequency = 433.9200000 MHz). Presumably, the base listens on this channel immediately after being plugged in.
Side note: A limitation of the system is that the RF channel is chosen purely randomly, and although there are 9999 possible channels, collisions are possible.
Prototyping a receiver
With the protocol understood, I sought to build a receiver that could decode commands from my physical Tempur-Pedic remote. I chose the following Si443x transceiver breakout for prototyping: https://www.tindie.com/products/modtronicsaustralia/rfm22b-wireless-breakout-board-800m-range/ ($22.95). Eventually I am actually going to switch to the Si446x (example breakout), since the Si443x is end-of-life.
The microcontroller I am using is the ESP32 ($21.95): https://www.sparkfun.com/products/13907. It is the perfect choice because it supports Bluetooth Low Energy and WiFi, one/both of which I’m planning to use for the next part of this project wherein I build my own Android app for controlling my base.
You can find the (work in progress) code is here: https://bitbucket.org/MostThingsWeb/temper-bridge/src/master/main/
I will be posting more details on it soon (hopefully).
The receiver code is nearly feature complete, so next I will be working on the transmitter portion. After that, I will be working on the Bluetooth Low Energy interface and an Android app.
34 thoughts on “Reverse engineering the Tempur-Pedic adjustable base remote control (part 1)”
Fantastic job here. I love to see detailed reverse engineering like this. Despite being a ham, I’ve only messed with the RTLSDR, but seeing a waveform is completely different from decoding that waveform, especially in light of all the different modulation schemes. Newer devices are even harder, smaller, have newer chipsets, more integrated designs, and so on. I was glad to see you go the logic analyzer route, because it seems to make it a lot easier. I enjoy doing that, but there can be challenges there too, so I appreciate the hard work and the sharing with us!!
I’ve been working on-again/off-again on reversing a sega genesis Z80 communication to the Yamaha sound chips.
See here if you’re interested. https://www.techtravels.org/2017/12/watching-ym2612-communication-on-sega-genesis/
Thanks again for sharing and great job, very interesting!
Hi Keith, not sure why I never replied to this. Thanks so much for your kind words 🙂
No worries! It happens sometimes to me my blog — but I try not to! Thanks again!
Wow, incredible! I just found this while looking up the Tempur-pedic Wi-Fi module on Google.
I deeeeeeply want to encourage you to keep going. Having an app or any additional ways to control my bed would be incredible.
Thanks! I assure you it will be finished, eventually :). I just started back up looking at it recently. Out of curiosity, when it is finished, do you think you’d build the interface board yourself, or would you be interested in a pre-assembled board? No promises just want to gauge interest.
I can’t build anything. Nor can I code anything. Sooooo. 😁
I’ll tentatively mark that down as one vote for pre-assembled kit 🙂
I have the skills but little time so mark one more vote for interest in a pre-assembled kit 😉
Roger that. Now let’s just see if I can find the time 🙂
Hi Corey, it’s been a while but I’ve now got a product for sale based on this :). https://www.temperbridge.com/product/temperbridge-assembled. It targets Home Assistant.
Found this in my hunt for something to control my bed via Alexa and Home Assistant. This would be a great start. Interested in any progress you made.
No noteworthy progress recently, but I’m back actively working on it. Stay tuned!
I’ll be watching. I tried to download and build the code but haven’t built a c project in years and don’t know cmake so having a bit of trouble getting it to compile.
Make sure you have completed the ESP32 set up guide: https://docs.espressif.com/projects/esp-idf/en/latest/get-started/. The code right now is a bit of a hodgepodge. As covered in the blog post, the remote uses the Si4431 wireless transceiver, but I have actually decided to utilize the Si446x family – the repository right now represents this state of transition. I am making the switch because the Si443x family is not recommended for new designs (by the manufacturer), and is getting a bit hard to find on Mouser and DigiKey. (The Si4431 is now ~$6 a chip on Mouser, whereas Si4463 is ~$3).
If you want to tinker with the code yourself, I’d recommend sticking with the Si4431 for now. I have not figured out how to receive packets with the Si446x, let alone transmit any. Checkout commit 86f7c5b (https://bitbucket.org/MostThingsWeb/temper-bridge/commits/86f7c5b16aab0e791fced58fbe756795f41267ce), since it is just before I started diverting my efforts towards Si446x.
Feel free to email me if you have any questions and I’ll try to help as time allows :). Email is chris @ my domain name (excluding the “blog.” part).
Hi Ron, I finally have a Home Assistant product out. Please check it out here: https://www.temperbridge.com/product/temperbridge-assembled
That’s great. I am interested in getting one. I do have a split bed but I think I can manage the setup in home assistant to create some virtual switches and automations to change channel. Can you provide some details on how the channel is changed, is modifying or configuring the code required or can I do it all in Home Assistant?
Also I see the home assistant screen shots on your product page but what I would really like to see is what entities get created so I can figure out how I would write some automations.
With information on how channel is changed and how entities are triggered I think I would be all set if figuring this out.
Sorry for the delay – for some reason I wasn’t getting emails about new comments on my blog. The channel is configured directly in Home Assistant as an entity (specifically, it is just a ‘number’ entity). No code changes are required for usage of TemperBridge :).
I am working on writing up documentation here about how to configure things in Home Assistant. It will go into detail about the entities. I hope to have the docs completed soon, but if you want, you can shoot me an email (chris @ this domain name without the blog.) and we can chat directly :). (Same goes for anyone reading this that has questions about TemperBridge).
Any idea if this protocol is the same as the one used by the rc-wm-101 remote? (fcc id VFKRC-WM-101)
We got a tempur bed in probably 2013 or 2014 and no wifi is built in at all, just RF.
I have an old raspberry pi sitting around. I maybe could run a web server on it and then could get some kind of RF module for it that would adjust the bed (at least the 4 memories and the flat button) in response to clicking buttons on a web page. Should be relatively easy if I could send the right RF signals to the bed from the Pi.
I’m really resistant to pay tempur $100+ for a new one of their crappy remotes and while our remote still works, it’s flaky if you don’t hold it just the right way and the batteries shift in the battery compartment. Plus I’d rather just control basic functions with my phone.
It’s most likely a different protocol.
Is this still a thing? If be in for a kit as well.
Maybe eventually, but I haven’t actively worked on it for a couple months :/
Hi Nathan, you may be interested in my latest blog post and new Patreon: https://www.patreon.com/chrislaplante. Thanks, Chris
Hi Nathan, I’ve now got a product for sale based on this :). https://www.temperbridge.com/product/temperbridge-assembled. It targets Home Assistant.
Chris, Once you finish your Android App, how difficult would it be for you to develop a Control4 plug-in for the Tempur-ERGO? It would be very cool if bed states could be incorporated into Control4 Scenes.
It wasn’t something I was initially planning, but eventually maybe. I haven’t done much with this project since I last posted, but you’re the second person this week to express interest in it, so I’m going to try to make a concerted effort to push it forward.
I’m interested as well! 🙂
Hi Steve, thanks for the interest! You may be interest in my latest blog post and new Patreon: https://www.patreon.com/chrislaplante. Thanks again, Chris
Me too! Thank you!
Hi Warren, glad to hear! You may be interested in following my Patreon for additional updates: https://www.patreon.com/chrislaplante . Thanks, Chris
Hi Warren, I’ve now got a product for sale based on this :). https://www.temperbridge.com/product/temperbridge-assembled. It targets Home Assistant.
There are products like the BroadLink RM4 pro https://www.BroadLink.com and the towide transmitter https://www.amazon.com/dp/B09FKZLJY6?ref_=cm_sw_r_cp_ud_dp_3N5ASGGPRGJM6CZ7CMTD
Have you worked with either product or others that already have some features? What are the difficulties you see in implementation of a generic solution?
It’s a good question, but I have no experience with the RM4 pro or similar devices. It may be possible but it’s not something I have investigated. At the very least, I think you’d need to manually ‘teach’ the RM4 each individual button. TemperBridge (https://www.temperbridge.com/) already has the codes programmed in. All you need to do is configure the channel.
Hi Chris, If you ever get a successful Control4 driver for the Ergo Extend, I’d gladly pay you a few hundred dollars for a license of it. Bob
Thanks for your comment! I don’t currently have any plans to write a Control4 driver myself, but I think it could be accomplished relatively easily by someone using a TemperBridge and the integrated ESPHome API (https://esphome.io/components/api.html). Alternatively, maybe it would be possible for Control4 to interact with Home Assistant.