├── bin └── placeholder ├── AUTHORS ├── .gitignore ├── makefile ├── LICENSE ├── README.md └── nrf24-btle-decoder.c /bin/placeholder: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Omri Iluz https://github.com/omriiluz 2 | Tobias Schramm https://github.com/TobleMiner 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | 5 | # Libraries 6 | *.lib 7 | *.a 8 | 9 | # Shared objects (inc. Windows DLLs) 10 | *.dll 11 | *.so 12 | *.so.* 13 | *.dylib 14 | 15 | # Executables 16 | *.exe 17 | *.out 18 | *.app 19 | bin 20 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | 2 | SHELL = /bin/sh 3 | UNAME_S := $(shell uname -s) 4 | 5 | ifeq ($(UNAME_S),Darwin) 6 | CC = g++ 7 | else 8 | CC = gcc 9 | FLAGS = -std=gnu99 10 | endif 11 | 12 | CFLAGS = -Wall -O3 13 | OUTDIR = ./bin 14 | 15 | TARGET = $(OUTDIR)/nrf24-btle-decoder 16 | SOURCES = nrf24-btle-decoder.c 17 | 18 | all: $(TARGET) 19 | 20 | $(TARGET): $(SOURCES) 21 | $(CC) $(FLAGS) $(CFLAGS) -o $(TARGET) $(SOURCES) 22 | 23 | clean: 24 | -rm -f $(TARGET) 25 | -rm -f $(TARGET).exe 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Omri Iluz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | NRF24-BTLE-Decoder 2 | ================== 3 | 4 | Sniff and decode NRF24L01+ and Bluetooth Low Energy using RTL-SDR. 5 | These protocols use the ISM 2.4Ghz frequency range, which is beyond the capabilities of the cheap rtl-sdr, a down convertor is necessary. See http://blog.cyberexplorer.me/2014/01/sniffing-and-decoding-nrf24l01-and.html for more details. 6 | 7 | The main repository is at https://github.com/omriiluz/NRF24-BTLE-Decoder 8 | 9 | Compile 10 | ------- 11 | `make` 12 | or directly 13 | `gcc -std=gnu99 -Wall -O3 -o nrf24-btle-decoder nrf24-btle-decoder.c` 14 | 15 | Usage 16 | ----- 17 | `nrf24-btle-decoder [-t nrf|btle] [-d 1|2|8] [-l len]` 18 | -t packet_type (nrf or btle), defaults to nrf. Using packet type btle implies -d 2 19 | -d downsample_rate (1 for 2mbps, 2 for 1mbps, 8 for 256kbps), default to 2 20 | -l len (1-32). Sets a fixed packet length 21 | 22 | Important - this program input is a 2M samples per second bitstream generated by rtl_fm or equivalent e.g. rtl_fm.exe -f 428m -s 2000k | nrf24-btle-decoder.exe -t nrf -s 2 23 | 24 | Dependencies 25 | ------------ 26 | * working rtl-sdr library. See http://sdr.osmocom.org/trac/wiki/rtl-sdr 27 | * working hardware - rtl-sdr, downconverter, antenna 28 | 29 | Limitations 30 | ----------- 31 | * The NRF24L01+ protocol decoder is missing 0/1 byte CRC. It should be trivial to implement, please open an issue if you need that capability. 32 | * The BTLE protocol decoder currently supports only advertisement packets on channel 38 and not data packets / frequency hopping. I am still evaluating whether the rtl-sdr hardware is fast enough to track the frequency hopping. 33 | 34 | Troubleshooting 35 | ----------------- 36 | * Biggest problem is noise, avoid rf auto gain and set as low as possible. I usually get best results with 0-10 db gain. 37 | * Second biggest problem is frequency drift. Use kalibrate for a good base line then fine tune the frequency in 50Khz steps until perfect 38 | 39 | License 40 | ------- 41 | All of the code contained here is licensed by the MIT license. 42 | 43 | Credit 44 | ------ 45 | Dmitry Grinberg, CRC and Whiten code for BTLE - http://goo.gl/G9m8Ud 46 | Open Source Mobile Communication, RTL-SDR information - http://sdr.osmocom.org/trac/wiki/rtl-sdr 47 | Steve Markgraf, RTL-SDR Library - https://github.com/steve-m/librtlsdr 48 | 49 | ----------------- 50 | 51 | Copyright (c) 2014 Omri Iluz (omri@il.uz / http://cyberexplorer.me / https://github.com/omriiluz) 52 | -------------------------------------------------------------------------------- /nrf24-btle-decoder.c: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2014 Omri Iluz (omri@il.uz / http://cyberexplorer.me) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | this software and associated documentation files (the "Software"), to deal in 8 | the Software without restriction, including without limitation the rights to 9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software is furnished to do so, 11 | subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | CREDITS: 24 | Dmitry Grinberg, CRC and Whiten code for BTLE - http://goo.gl/G9m8Ud 25 | Open Source Mobile Communication, RTL-SDR information - http://sdr.osmocom.org/trac/wiki/rtl-sdr 26 | Steve Markgraf, RTL-SDR Library - https://github.com/steve-m/librtlsdr 27 | 28 | 29 | */ 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #if defined(WIN32) 40 | #include 41 | #include 42 | #endif /* defined(WIN32) */ 43 | #include // For exit function 44 | #include /* for getopt */ 45 | 46 | /* Global variables */ 47 | int32_t g_threshold; // Quantization threshold 48 | int g_srate; // sample rate downconvert ratio 49 | 50 | /* Ring Buffer */ 51 | #define RB_SIZE 1000 52 | int rb_head=-1; 53 | int16_t *rb_buf; 54 | /* Init Ring Buffer */ 55 | void RB_init(void){ 56 | rb_buf = (int16_t *)malloc(RB_SIZE*2); 57 | } 58 | /* increment Ring Buffer Head */ 59 | void RB_inc(void){ 60 | rb_head++; 61 | rb_head=(rb_head)%RB_SIZE; 62 | } 63 | /* Access Ring Buffer location l */ 64 | #define RB(l) rb_buf[(rb_head+(l))%RB_SIZE] 65 | /* end Ring Buffer */ 66 | 67 | /* helper functions */ 68 | /* Quantize sample at location l by checking whether it's over the threshold */ 69 | /* Important - takes into account the sample rate downconversion ratio */ 70 | inline bool Quantize(int16_t l){ 71 | return RB(l*g_srate) > g_threshold; 72 | } 73 | #define Q(l) Quantize(l) 74 | 75 | uint8_t inline SwapBits(uint8_t a){ 76 | return (uint8_t) (((a * 0x0802LU & 0x22110LU) | (a * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16); 77 | } 78 | 79 | /* Calcualte CRC16-CCITT for the NRF packets. 80 | * using custom start value to compensate for non byte aligned message (due to 9 bit PCF) 81 | */ 82 | uint32_t NRFCrc(const uint8_t* data, size_t data_len) 83 | { 84 | uint8_t i; 85 | bool bit; 86 | uint8_t c; 87 | uint_fast16_t crc=0x3C18; 88 | while (data_len--) { 89 | c = *data++; 90 | for (i = 0x80; i > 0; i >>= 1) { 91 | bit = crc & 0x8000; 92 | if (c & i) { 93 | bit = !bit; 94 | } 95 | crc <<= 1; 96 | if (bit) { 97 | crc ^= 0x1021; 98 | } 99 | } 100 | crc &= 0xffff; 101 | } 102 | return (uint16_t)(crc & 0xffff); 103 | } 104 | 105 | /* Calcualte custom CRC24 for BTLE */ 106 | uint32_t BTLECrc(const uint8_t* data, uint8_t len, uint8_t* dst){ 107 | 108 | uint8_t v, t, d; 109 | uint32_t crc=0; 110 | while(len--){ 111 | 112 | d = SwapBits(*data++); 113 | for(v = 0; v < 8; v++, d >>= 1){ 114 | 115 | t = dst[0] >> 7; 116 | 117 | dst[0] <<= 1; 118 | if(dst[1] & 0x80) dst[0] |= 1; 119 | dst[1] <<= 1; 120 | if(dst[2] & 0x80) dst[1] |= 1; 121 | dst[2] <<= 1; 122 | 123 | 124 | if(t != (d & 1)){ 125 | 126 | dst[2] ^= 0x5B; 127 | dst[1] ^= 0x06; 128 | } 129 | } 130 | } 131 | for (v=0;v<3;v++) crc=(crc<<8)|dst[v]; 132 | return crc; 133 | } 134 | 135 | /* whiten (descramble) BTLE packet using channel value */ 136 | void BTLEWhiten(uint8_t* data, uint8_t len, uint8_t chan){ 137 | 138 | uint8_t i; 139 | uint8_t lfsr = SwapBits(chan) | 2; 140 | while(len--){ 141 | for(i = 0x80; i; i >>= 1){ 142 | 143 | if(lfsr & 0x80){ 144 | 145 | lfsr ^= 0x11; 146 | (*data) ^= i; 147 | } 148 | lfsr <<= 1; 149 | } 150 | data++; 151 | } 152 | } 153 | 154 | /* Extract quantization threshold from preamble sequence */ 155 | int32_t ExtractThreshold(void){ 156 | int32_t threshold=0; 157 | int c; 158 | for (c=0;c<8*g_srate;c++){ 159 | threshold+=(int32_t)RB(c); 160 | } 161 | return (int32_t)threshold/(8*g_srate); 162 | } 163 | 164 | /* Identify preamble sequence */ 165 | bool DetectPreamble(void){ 166 | int transitions=0; 167 | int c; 168 | 169 | /* preamble sequence is based on the 9th symbol (either 0x55555555 or 0xAAAAAAAA) */ 170 | if (Q(9)){ 171 | for (c=0;c<8;c++){ 172 | transitions += Q(c)>Q(c+1); 173 | } 174 | } else { 175 | for (c=0;c<8;c++){ 176 | transitions += Q(c)>((6-c)*8))&0xFF; 206 | } 207 | 208 | for (c=0;c>=7; 292 | 293 | /* extract packet length, avoid excessive length packets */ 294 | if(packet_length == 0) 295 | packet_length=(int)pcf>>3; 296 | if (packet_length>32) return false; 297 | 298 | /* extract data */ 299 | ExtractBytes(6*8+9, packet_data, packet_length); 300 | 301 | /* Prepare packed bit stream for CRC calculation */ 302 | PackPacket(packet_addr_l, pcf, packet_data, packet_length, packet_packed); 303 | 304 | /* calculate packet crc */ 305 | calced_crc=NRFCrc(packet_packed, 7+packet_length); 306 | 307 | /* extract crc */ 308 | ExtractBytes((6+packet_length)*8+9, tmp_buf, 2); 309 | packet_crc = tmp_buf[0]<<8 | tmp_buf[1]; 310 | 311 | /* NRF24L01+ packet found, dump information */ 312 | if (packet_crc==calced_crc){ 313 | gettimeofday(&tv, NULL); 314 | printf("%ld.%06ld ", (long)tv.tv_sec, tv.tv_usec); 315 | printf("NRF24 Packet start sample %"PRId32", Threshold:%"PRId32", Address: 0x%08"PRIX64" ",sample,g_threshold,packet_addr_l); 316 | printf("length:%d, pid:%d, no_ack:%d, CRC:0x%04X data:",packet_length,(pcf&0b110)>>1,pcf&0b1,packet_crc); 317 | for (c=0;c 32){ 390 | fprintf (stderr, "illegal packet length - %d.\n\n", packet_len); 391 | optfail=1; 392 | } 393 | break; 394 | case 'h': 395 | default: 396 | usage(); 397 | break; 398 | } 399 | } 400 | if (decode_type==2) srate=2; 401 | 402 | if (argc < optind || optfail) usage(); 403 | 404 | RB_init(); 405 | g_threshold = 0; 406 | 407 | skipSamples=1000; 408 | time_t start_time = time(NULL); 409 | 410 | while(!feof(stdin) ) { 411 | cursamp = (int16_t) ( fgetc(stdin) | fgetc(stdin)<<8); 412 | RB_inc(); 413 | RB(0)=(int)cursamp; 414 | if (--skipSamples<1)if (DecodePacket(decode_type, ++samples, srate, packet_len)) skipSamples=20; 415 | } 416 | 417 | printf("%"PRId32" samples received in %d seconds \n",samples,(int)(time(NULL)-start_time)); 418 | return 0; 419 | } 420 | --------------------------------------------------------------------------------