In a previous post I talked about the process of using both MATLAB and GNU Radio to process data in real time. I recently used this process to put together a demonstration on how you could use an RTL-SDR to sense and decode the information your Keyless Entry remote sends to your car. This is a pretty popular demonstration of software-defined radio, Adam Laurie and Balint Seeber have put together similar demos.
Eventually I want to get the entire thing working with just GNU Radio but the hybrid approach is working well for now. It also shows how such a hybrid approach might be useful for other software-defined radio applications.
Keyless remote entry systems have been around for a while and likely aren’t going away any time soon. Your remote can send commands to your car wirelessly using a small radio transmitter to tell your car to unlock, lock, etc.
These remotes vary between auto manufacturers but for the most part they transmit at 315MHz and use On-Off Keying (OOK), a very simple form of digital modulation. OOK sends an RF signal of a certain length to represent a ‘1’ and stays silent to represent a ‘0’.
For security reasons the remote doesn’t send the same signal each time. The remote encrypts its commands using a rolling key so the bits representing each command are different each time. Your car and remote share the same private key which makes it so only your car can decode the encrypted transmission.
This begs the question, what happens when I press a button when I’m out of range of my car? Doesn’t that get the two rolling keys out of sync? Your car actually calculates the rolling key for the next transmission it expects as well as the next 256 transmissions (that number may vary between manufacturers). If any of the 256 match the received signal from your remote, the car will unlock and resynchronize. Of course, if you use your remote more than 256 times while out of range of your car the two will get out of sync. In that case there is usually a procedure in the owner’s manual to get them synced up again.
To receive the signal I’ll use an RTL-SDR dongle tuned to 315MHz and running at the default sample rate of 2Msps. In GNU Radio I’ll decimate and lowpass filter the received signal. Since the modulation is OOK, I will just take the magnitude of the received signals.
That data will be written into a FIFO as previously discussed. I will continue the rest of the processing in MATLAB.
Getting The Bits
In MATLAB I will begin by opening and reading samples from the FIFO. As per the previous post, I’ll make sure that I start the flow graph running before trying to read samples from the FIFO, or MATLAB will lock up.
fi = fopen('data/keyless_mag_fifo','rb'); raw = fread(fi, buffer_len, 'float');
This will give a chunk of floating point samples of length
buffer_len (in this case 50,000) containing transmissions and silence. I’ll use a simple energy detection to figure out the start of the frame.
eng_mat = vec2mat(raw,10); x_mag = abs(eng_mat).^2; x_sum = sum(x_mag,2); b = x_sum(2:end); a = x_sum(1:end-1); x_diff = b./a; x_norm = x_diff./max(x_diff);
The stream is broken up into chunks of ten samples. The energy of each chunk is calculated by taking the sum of the magnitude squared. Two vectors containing the calculated energies are created with the vector
a lagging one chunk behind the vector
b. If I divide b by a the resulting vector
x_diff will contain peaks when the transmissions begin.
x_ind = find(x_norm>threshold); if (isempty(x_ind)) x_ind = 1; end start_ind = x_ind(1)*window_len;
If I then normalize the vector
x_diff so that the tallest peak has a value of 1, I can set a threshold (0.2 in this case) that I’ll consider the start of the peak. If I find a peak I can calculate the sample with which to start the packet.
I’ll then count out a number of samples after the start index (in this case 6,000) and save that as the packet. I’ll do a quick check that I have at least 6,000 samples left in the vector
raw. If not, I’ll check if there are more samples available in the buffer and grab those. I’ll then remove those samples from the vector
if (start_ind+pkt_len)>length(raw) in_buff = fread(fi, buffer_len, 'float'); if (isempty(in_buff)) %display('Buffer Empty') continue end raw = cat(1,raw,in_buff); end x_pkt = raw(start_ind:start_ind+pkt_len); raw = raw(start_ind+pkt_len+1:end);
After processing these samples I’ll return to read 6,000 more samples from
raw until the vector is empty at which point I’ll grab 50,000 more samples from the buffer and store it in
At this point I can take a closer look at the packet extracted from
raw. Looking at the first 250 samples I can see the on-off keying more clearly.
I’ll then filter the signal with an integrator to smooth out the plateaus and silences that comprise the OOK signal.
x_filt = filter(ones(1,n)/n,1,x_pkt);
Then I’ll use a threshold to filter the further so each sample is either a one or a zero.
x_dec = x_filt; x_dec(x_dec>0.3) = 1; x_dec(x_dec~=1) = 0;
In this case the threshold used is 0.3. I’ll then cut the beginning of the packet so that the first sample is the start of the first plateau.
x_ind = find(x_dec); if (isempty(x_ind)) continue; end start_ind = x_ind(1); x_dec = x_dec(start_ind:end);
Next I need to convert the OOK pulses into bits. For this I’ll count the duration of the plateaus and silences.
counter =0; bit_ind = 1; for ii=2:length(x_dec)-1 if (x_dec(ii)~=x_dec(ii-1)) % Transition counter = 0; else counter=counter+1; if (counter>16) counter=0; bit(bit_ind) = x_dec(ii); bit_ind=bit_ind+1; end end end
After a high-low or low-high transition we’ll start a counter. If the counter reaches a value (16 in this case) without another transition occurring I’ll store the bit.
The bits can then be converted to bytes using MATLAB’s
bit_group = vec2mat(bit,8); byte = bi2de(bit_group)';
Decoding The Signal
The last part is to make sense of all the bytes that are being transmitted. In addition to the encryption mentioned above, the format of the packet is almost entirely different for each car manufacturer. The structure below pertains to my Saturn, but your car may be different.
I found the packet starts off with thirteen bytes of value 85 (alternating ones and zeros in binary) to synchronize the transmission. I start by finding these bytes and keeping everything after them as the payload.
known_sync = 85*ones(1,13); if (length(byte)<14) continue end sync = byte(1:13); payload = byte(14:end);
If the sync is found I flag the packet as good and go on to display the results. If the sync wasn’t correctly found I skip this packet and go back to the start of the loop to grab more data.
pkt_good = false; if (isequal(sync,known_sync)) display('Received Pkt') pkt_good=true; end if (~pkt_good) continue end
Unfortunately due to the encryption this is where the decoding stops. What I have next is a sequence of approximately 20 bytes that correspond to a command transmitted to my car. However, due to the encryption I cannot tell what command is being sent. Instead I just display the sync code and the data along with a message that the signal was received. The data stays around for a few seconds and then fades.
While this project shows a good example of using both MATLAB and GNU Radio, for this particular application it would probably be best to use one or the other. I would like to eventually transition all the signal processing over to GNU Radio. My other option would be to interface directly with the RTL-SDR in MATLAB using the Communication Systems Toolbox to control the RTL-SDR. Processing the data using MATLAB’s object-oriented programming features would also improve the efficiency, but that might just have to be a project for another day.
Until then, the code for this project can be found on GitHub.