Summary
In part 6 of this series I will update the AnyCloud BLE Advertising Scanner to decode advertising packets into a more human readable textual output
Story
We are now 6 (or maybe 7 depending on how you count) articles into this series and we are still looking at raw bytes. I have gotten to where I am pretty good at understanding those bytes, but that is now way to roll. You might remember from the article on the IoT Expert Bluetooth Utility library that there were a some interesting functions defined in the header. Here it is:
wiced_bool_t btutil_isEddystone(uint8_t *data); wiced_bool_t btutil_is_iBeacon(uint8_t *data); wiced_bool_t btutil_isCypress(uint8_t *data); int btutil_adv_len(uint8_t *packet); void btutil_adv_printPacketDecode(uint8_t *packet); void btutil_adv_printPacketBytes(uint8_t *packet);
Lets transform our project from part 6 to use these functions. In this article I will
- Redo the print bytes (to be smarter) functionality and to use the built in function
- Rework the logic for the “print” command implementation
- Add a new command “decode” which will run the decode function
There are
Article | Topic |
---|---|
AnyCloud Bluetooth Advertising Scanner (Part 1) | Introduction to AnyCloud Bluetooth Advertising |
AnyCloud Bluetooth Advertising Scanner (Part 2) | Creating an AnyCloud Bluetooth project |
AnyCloud Bluetooth Advertising Scanner (Part 3) | Adding Observing functionality to the project |
AnyCloud Bluetooth Utilities Library | A set of APIs for enhancement of the AnyCloud Library |
AnyCloud Bluetooth Advertising Scanner (Part 4) | Adding a command line to the scanner |
AnyCloud Bluetooth Advertising Scanner (Part 5) | Adding a history database to the scanner |
AnyCloud Bluetooth Advertising Scanner (Part 6) | Decoding advertising packets |
AnyCloud Bluetooth Advertising Scanner (Part 7) | Adding recording commands to the command line |
AnyCloud Bluetooth Advertising Scanner (Part 8) | Adding filtering to the scanner |
AnyCloud Bluetooth Advertising Scanner (Part 9) | Improve the print and add packet age |
AnyCloud Bluetooth Advertising Scanner (Part 10) | Sort the database |
All of the code can be found at git@github.com:iotexpert/AnyCloudBLEScanner.git and https://github.com/iotexpert/AnyCloudBLEScanner.git
There are git tags in place starting at part 5 so that you can look at just that version of the code. "git tag" to list the tags. And "git checkout part6" to look at the part 6 version of the code.
You can also create a new project with this is a template if you have the IoT Expert Manifest Files installed
Replace two code blocks with function calls
Do you remember this block of code? It print’s out the 6-bytes of the Bluetooth Address.
for(int i=0;i<BD_ADDR_LEN;i++) { printf("%02X:",adb_database[entry].result->remote_bd_addr[i]); }
You might have noticed that this function already exists in the BT Utility Library. Use it.
btutil_printBDaddress(adb_database[entry].result->remote_bd_addr);
Then you remember this block of code which iterates through and advertising packet and prints out the raw bytes?
// Print the RAW Data of the ADV Packet printf(" Data: "); int i=0; while(adb_database[entry].data[i]) { for(int j=0;j<adb_database[entry].data[i];j++) { printf("%02X ",adb_database[entry].data[i+1+j]); } i = i + adb_database[entry].data[i]+1; }
Well, it exists in the library as well. Use it.
btutil_adv_printPacketBytes(adb_database[entry].data);
Fix the “print” Command
In the previous implementation I had two functions for “print”. The first one printed one entry and the second one printed the whole table. I decided that I didnt really like this logic, so I compressed those two functions into one function. Specifically, it take a number “entry”. If that number is -1 then it will print the whole table.
static void adb_db_printRawPacket(int entry) { int start,end; if(entry <= -1) { start = 0; end = adb_db_count; } else { start = entry; end = entry; } if(end>adb_db_count) end = adb_db_count; for(int i=start;i<=end;i++) { printf("%02d MAC: ",i); btutil_printBDaddress(adb_database[i].result->remote_bd_addr); printf(" Data: "); btutil_adv_printPacketBytes(adb_database[i].data); printf("\n"); } }
Add a new “decode” Command
The next thing to do is to add a function to print out decoded packets (or the whole table). So I wrote this:
static void adb_printDecodePacket(int entry) { int start,end; if(entry == -1) { start = 0; end = adb_db_count; } else { start = entry; end = entry; } if(end>adb_db_count) end = adb_db_count; for(int i=start;i<=end;i++) { printf("%02d MAC: ",i); btutil_printBDaddress(adb_database[i].result->remote_bd_addr); printf("\n"); btutil_adv_printPacketDecode(adb_database[i].data); printf("\n"); } }
After finishing that block of code, I realized I had implemented almost exactly the same functionality which two different functions. So, I decided to redo this by doing this. Notice that it take in a adb_print_method, in other words raw bytes or decoded packet.
typedef enum { ADB_PRINT_METHOD_BYTES, ADB_PRINT_METHOD_DECODE, } adb_print_method_t; static void adb_db_print(adb_print_method_t method,int entry) { int start,end; if(entry < 0) { start = 0; end = adb_db_count; } else { start = entry; end = entry; } if(end>adb_db_count) end = adb_db_count; for(int i=start;i<=end;i++) { printf("%02d MAC: ",i); btutil_printBDaddress(adb_database[i].result->remote_bd_addr); switch(method) { case ADB_PRINT_METHOD_BYTES: printf(" Data: "); btutil_adv_printPacketBytes(adb_database[i].data); break; case ADB_PRINT_METHOD_DECODE: printf("\n"); btutil_adv_printPacketDecode(adb_database[i].data); break; } printf("\n"); } }
I don’t show it here, but after changing this I had to fix up the function calls in several places in the advDatabase.
Add a new command to print decode packets
Now that I have a new method to print packets, I add a command to the database to allow the user to call it:
typedef enum { ADB_ADD, ADB_PRINT_RAW, ADB_PRINT_DECODE, } adb_cmd_t;
Then in the queue loop:
switch(msg.cmd) { case ADB_ADD: scan_result = (wiced_bt_ble_scan_results_t *)msg.data0; data = (uint8_t *)msg.data1; adb_db_add(scan_result,data); break; case ADB_PRINT_RAW: adb_db_print(ADB_PRINT_METHOD_BYTES,(int)msg.data0); break; case ADB_PRINT_DECODE: adb_db_print(ADB_PRINT_METHOD_DECODE,(int)msg.data0); break; }
Then the actual function
void adb_printDecode(int entry) { adb_cmdMsg_t msg; msg.cmd = ADB_PRINT_DECODE; msg.data0 = (void *)entry; xQueueSend(adb_cmdQueue,&msg,0); // If the queue is full... oh well }
Then add it to advDatabase.h
void adb_printDecode(int entry);
Finally to the usercmd.c
static int usrcmd_printDecode(int argc, char **argv) { if(argc == 1) { adb_printDecode(-1); } if(argc == 2) { int val; sscanf(argv[1],"%d",&val); adb_printDecode(val); } return 0; }
Program and Test
When I actually program the scanner you can see that I can print out 1 item. OR I can decode one item. Notice that one contains 3 fields
- flags
- Tx Power Level
- Manufacturers data. Apparently an Apple something or the other
And I can print the whole table
Or decode the whole table.