Read advertisement packet in Android
This is what I was looking for:
The BLE scan API BluetoothAdapter.startLeScan(ScanCallback) requires a call back function for the scan results. the method needs to look like the following:
private BluetoothAdapter.LeScanCallback ScanCallback =
new BluetoothAdapter.LeScanCallback()onLeScan(final BluetoothDevice device,
int rssi,
final byte[] scanRecord)
{...}
And the scanRecord variable is a byte array which contains the Advertisement packet payload.
Per the BLE specification the structure of the payload is very simple as follows:
The packets can be up to 47 bytes in length and consist of:
- 1 byte preamble
- 4 byte access address
- 2-39 bytes advertising channelPDU
- 3 bytes CRC
For advertisement communication channels, the access address is always 0x8E89BED6.
The PDU in turn has its own header (2 bytes: size of the payload and its type – whether the device supports connections, etc.) and the actual payload (up to 37 bytes).
Finally, the first 6 bytes of the payload are the MAC address of the device, and the actual information can have up to 31 bytes.
the format of the actual information is as follows:
first byte is length of the data and second byte is type followed by the data.
This is a clever way to allow any application to skip entire data records if they don't care about the contents.
Here is the sample code to determine the contents of the Advertisement packet:
parseAdvertisementPacket(final byte[] scanRecord) {
byte[] advertisedData = Arrays.copyOf(scanRecord, scanRecord.length);
int offset = 0;
while (offset < (advertisedData.length - 2)) {
int len = advertisedData[offset++];
if (len == 0)
break;
int type = advertisedData[offset++];
switch (type) {
case 0x02: // Partial list of 16-bit UUIDs
case 0x03: // Complete list of 16-bit UUIDs
while (len > 1) {
int uuid16 = advertisedData[offset++] & 0xFF;
uuid16 |= (advertisedData[offset++] << 8);
len -= 2;
uuids.add(UUID.fromString(String.format(
"%08x-0000-1000-8000-00805f9b34fb", uuid16)));
}
break;
case 0x06:// Partial list of 128-bit UUIDs
case 0x07:// Complete list of 128-bit UUIDs
// Loop through the advertised 128-bit UUID's.
while (len >= 16) {
try {
// Wrap the advertised bits and order them.
ByteBuffer buffer = ByteBuffer.wrap(advertisedData,
offset++, 16).order(ByteOrder.LITTLE_ENDIAN);
long mostSignificantBit = buffer.getLong();
long leastSignificantBit = buffer.getLong();
uuids.add(new UUID(leastSignificantBit,
mostSignificantBit));
} catch (IndexOutOfBoundsException e) {
// Defensive programming.
Log.e("BlueToothDeviceFilter.parseUUID", e.toString());
continue;
} finally {
// Move the offset to read the next uuid.
offset += 15;
len -= 16;
}
}
break;
case 0xFF: // Manufacturer Specific Data
Log.d(TAG, "Manufacturer Specific Data size:" + len +" bytes" );
while (len > 1) {
if(i < 32) {
MfgData[i++] = advertisedData[offset++];
}
len -= 1;
}
Log.d(TAG, "Manufacturer Specific Data saved." + MfgData.toString());
break;
default:
offset += (len - 1);
break;
}
}
thanks to
how-ibeacons-work
bluetooth org specs
mass for putting me on the right direction!
Can an iBeacon have a data payload
A few possibilities:
You can tack on one extra data byte to the end of the iBeacon transmission before it reaches its max advertisement length. This byte cannot be read by iOS devices, though, because Apple blocks reading raw data of iBeacon adverts. It would work on Android/Mac/Linux.
You can interleave a second advertisenent with mostly data fields and line the two up with a common identifier like the minor. The more bytes you allocate to lining up the advertisements, the fewer you have to use for data. You can't use the mac tobline them up, because that is unreadable in iOS for the iBeacon transmission.
You can make the beacon connectable via GATT, and read data fields with GATT attributes. The beacon will stop advertising, though, when connected. This limits throughput and reliability.
All of these options require you to build a custom BLE beacon that does multiple advertisements. It is not a trivial undertaking.
Get advertisement data for BLE in iOS
Unfortunately, iOS does not allow you to access the raw advertisement data. I wrote a blog post demonstrating this. While the post is specifically about iBeacons, it applies to any BLE advertisement.
EDIT: To clarify, you can read the raw manufacturer data bytes or service data bytes of non-iBeacon advertisements. It is only the iBeacon advertisements that have their manufacturer data bytes hidden by CoreLocation
. See here: Obtaining Bluetooth LE scan response data with iOS
The equivalent MacOS CoreLocation methods do allow this, so it is probably an intentional security or power saving restriction on iOS.
Attaching advertising payload for iOS without pairing
Unfortunately, you cannot use CoreBluetooth APIs to attach data to advertisements. On iOS the CBAdvertisementDataServiceDataKey is read-only. While Bluetooth LE allows attaching service data , Apple effectively disallows 3rd party apps from doing this.
You do have a few options:
Encode your data inside a 128-bit service UUID and advertise that. You will need to reserver a byte or two in the UUID to know that it is "your" advertisement, and therefore OK to decode the data from the other byes. This full UUID will only be advertised when your app is in the foreground visible on the screen. Let it go to the background or the screen turn off, and it will no longer advertise in that form. Similarly, receiving iOS devices must also be in the foreground with the screen on. This is because iOS disallows getting background scan results without specifying the matching service UUID up front. And because you are dynamically manipulating some of those bytes, you don't know what it will be.
Do a similar kind of encoding with the 4 byte major and minor fields inside the iBeacon BLE advertisement using CoreLocation. Again, this allows you to transmit only when the app is in the foreground. Receiving, however, can happen to a limited degree in the background (for 5-10 seconds after one of your beacons first appears when combining monitoring and ranging APIs.) The big disadvantage is you only have four bytes to work with.
Advertise data by manipulating the 128-bit background BLE Overflow Area Advertisement. This technique is more advanced, but advertising works in the background. Receiving works in the foreground, and partly in the background -- you can receive if the screen is at least turned on. You can read more about this technique and access free sample code in my blog post herehttp://www.davidgyoungtech.com/2020/05/07/hacking-the-overflow-area.
BLE obtain uuid encoded in advertising packet
If you want to get UUID / any other data e.g. Manufacturer Data out of scanRec[] bytes after BLE Scan, you first need to understand the data format of those Advertisement Data packet.
Came from Bluetooth.org:
Too much theory, want to see some code snippet? This function below would straight forward print parsed raw data bytes. Now, you need to know each type code to know what data packet refers to what information. e.g. Type : 0x09, refers to BLE Device Name, Type : 0x07, refers to UUID.
public void printScanRecord (byte[] scanRecord) {
// Simply print all raw bytes
try {
String decodedRecord = new String(scanRecord,"UTF-8");
Log.d("DEBUG","decoded String : " + ByteArrayToString(scanRecord));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
// Parse data bytes into individual records
List<AdRecord> records = AdRecord.parseScanRecord(scanRecord);
// Print individual records
if (records.size() == 0) {
Log.i("DEBUG", "Scan Record Empty");
} else {
Log.i("DEBUG", "Scan Record: " + TextUtils.join(",", records));
}
}
public static String ByteArrayToString(byte[] ba)
{
StringBuilder hex = new StringBuilder(ba.length * 2);
for (byte b : ba)
hex.append(b + " ");
return hex.toString();
}
public static class AdRecord {
public AdRecord(int length, int type, byte[] data) {
String decodedRecord = "";
try {
decodedRecord = new String(data,"UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
Log.d("DEBUG", "Length: " + length + " Type : " + type + " Data : " + ByteArrayToString(data));
}
// ...
public static List<AdRecord> parseScanRecord(byte[] scanRecord) {
List<AdRecord> records = new ArrayList<AdRecord>();
int index = 0;
while (index < scanRecord.length) {
int length = scanRecord[index++];
//Done once we run out of records
if (length == 0) break;
int type = scanRecord[index];
//Done if our record isn't a valid type
if (type == 0) break;
byte[] data = Arrays.copyOfRange(scanRecord, index+1, index+length);
records.add(new AdRecord(length, type, data));
//Advance
index += length;
}
return records;
}
// ...
}
After this parsing, those data bytes would make more sense, and you can figure out next level of decoding.
Can I read an iPhone beacon with Windows.Devices.Bluetooth.Advertisement.BluetoothLEManufacturerData
The most important thing you need to do to detect Beacons on Windows 10 is to use the new BluetoothLeAdvertisementWatcher
class.
The code in the question seems focussed on setting up a filter to look for only specific Bluetooth LE advertisements matching a company code and perhaps a UUID contained in the advertisement. While this is one approach, it isn't strictly necessary -- you can simply look for all Bluetooth LE advertisements, then decode them to see if they are beacon advertisements.
I've pasted some code below that shows what I think you want to do. Major caveat: I have not tested this code myself, as I don't have a Windows 10 development environment. If you try it yourself and make corrections, please let me know and I will update my answer.
private BluetoothLEAdvertisementWatcher bluetoothLEAdvertisementWatcher;
public LookForBeacons() {
bluetoothLEAdvertisementWatcher = new BluetoothLEAdvertisementWatcher();
bluetoothLEAdvertisementWatcher.Received += OnAdvertisementReceived;
bluetoothLEAdvertisementWatcher.Start();
}
private async void OnAdvertisementReceived(BluetoothLEAdvertisementWatcher watcher, BluetoothLEAdvertisementReceivedEventArgs eventArgs) {
var manufacturerSections = eventArgs.Advertisement.ManufacturerData;
if (manufacturerSections.Count > 0) {
var manufacturerData = manufacturerSections[0];
var data = new byte[manufacturerData.Data.Length];
using (var reader = DataReader.FromBuffer(manufacturerData.Data)) {
reader.ReadBytes(data);
// If we arrive here we have detected a Bluetooth LE advertisement
// Add code here to decode the the bytes in data and read the beacon identifiers
}
}
}
The next obvious question is how do you decode the bytes of the advertisement? It's pretty easy to search the web and find out the byte sequence of various beacon types, even proprietary ones. For the sake of keeping this answer brief and out of the intellectual property thicket, I'll simply describe how to decode the bytes of an open-source AltBeacon advertisement:
18 01 be ac 2f 23 44 54 cf 6d 4a 0f ad f2 f4 91 1b a9 ff a6 00 01 00 02 c5 00
This is decoded as:
- The first two bytes are the company code (0x0118 = Radius Networks)
- The next two bytes are the beacon type code (0xacbe = AltBeacon)
- The next 16 bytes are the first identifier 2F234454-CF6D-4A0F-ADF2-F4911BA9FFA6
- The next 2 bytes are the second identifier 0x0001
- The next 2 bytes are the third identifier 0x0002
- The following byte is the power calibration value 0xC5 -> -59 dBm
iBeacon advertising packet transmission time
This is a useful question in terms of getting an ideal lower bound on power usage by the device (excluding any compute power used by the device).
The BLE packet has a preamble of 1 byte, access address of 4 bytes, header of 2 bytes, MAC address of 6 bytes, data of up to 31 bytes, then a CRC of 3 bytes. That's a total of 46 bytes or 368 bits.
BLE has a supposed data rate of 1Mbit. According to this article, that excludes framing / error checking / connecting (although an advertising packet probably won't spend a lot of time connecting). So assuming the best case of 1Mbit=1024*1024, we can send 2849 advertising packets per second. That means each one is about 0.35 ms - in an ideal world. If the article is right, and the effective data rate is as much as 4x slower, it could be as long as 1.4 ms.
Android: iBeacon - read its advertisement (e.g txPower)
The transmit (TX) power of the OnyxBeacon, and as of any other BLE device that is iBeacon compatible, can be obtained from the advertising packet. This does not require connecting to the device. Also, the TX power value will vary depending on the power level, as the TX power value is calibrated for each power level of the device. The TX power is the last byte of the advertising payload. More information on the structure of the iBeacon packet can be found here: http://www.havlena.net/en/location-technologies/ibeacons-how-do-they-technically-work/
Here is a sample of how to parse an iBeacon packet and obtain the TX power:
BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice bluetoothDevice, final int rssi, final byte[] scanData) {
if (scanData[7] == 0x02 && scanData[8] == 0x15) { // iBeacon indicator
System.out.println("iBeacon Packet: %s", bytesToHexString(scanData));
UUID uuid = getGuidFromByteArray(Arrays.copyOfRange(scanData, 9, 25));
int major = (scanData[25] & 0xff) * 0x100 + (scanData[26] & 0xff);
int minor = (scanData[27] & 0xff) * 0x100 + (scanData[28] & 0xff);
byte txpw = scanData[29];
System.out.println("iBeacon Major = " + major + " | Minor = " + minor + " TxPw " + (int)txpw + " | UUID = " + uuid.toString());
}
}
};
public static String bytesToHexString(byte[] bytes) {
StringBuilder buffer = new StringBuilder();
for(int i=0; i<bytes.length; i++) {
buffer.append(String.format("%02x", bytes[i]);
}
return buffer.toString();
}
public static UUID getGuidFromByteArray(byte[] bytes)
{
ByteBuffer bb = ByteBuffer.wrap(bytes);
UUID uuid = new UUID(bb.getLong(), bb.getLong());
return uuid;
}
Related Topics
Sed Error:Bad Option in Substitution Expression
.Zshrc Config File Syntax Error
Write to a File After Piping Output from Tail -F Through to Grep
How Can a Process Try to Access Other Process's Memory in Linux Virtual Memory System
What's the Purpose of the Ud2 Opcode in the Linux Kernel
Compare Md5 Sums in Bash Script
How to Extract One Column from Multiple Files, and Paste Those Columns into One File
Why Does Docker Prompt "Permission Denied" When Backing Up the Data Volume
How to Run a Linux Command That Still Runs After I Close My Putty Ssh Session
Find Value from One CSV in Another One (Like Vlookup) in Bash (Linux)
How to Timeout a Group of Commands in Bash
Bash Completion for Path in Argument (With Equals Sign Present)
How to Make Static Linked Elf File to Load Ld_Preload .So
Shell Command to Update Pom File from a Variable