BLE obtain uuid encoded in advertising packet - android

Im trying to get UUID of ble device. I was following android developers guide and so far I can get only device name and rssi. Im trying to get Uuid of the device that comes to scanning method that looks like this:
public void onLeScan(final BluetoothDevice device, int rssi,byte[] scanRecord) {
ParcelUuid[] myUUid =device.getUuids();
for(ParcelUuid a :myUUid){
Log.d("UUID",a.getUuid().toString());
}
String s = new String(scanRecord);
int len = scanRecord.length;
String scanRecords =new String(scanRecord) ;
deviceMap.put(device.getName().toString(), rssi);
Message msg = MainActivity.myHandler.obtainMessage();
Bundle bundle = new Bundle();
bundle.putCharSequence("dev_name", device.getName().toString());
bundle.putCharSequence("rssi", Integer.toString(rssi));
msg.setData(bundle);
MainActivity.myHandler.sendMessage(msg);
}
this returns - btif_gattc_upstreams_evt: Event 4096

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.

As mentioned in comments, a BLE device doesn't really have a specific UUID (but rather many for included services). However, some schemes such as iBeacon encode a unique identifier in a manufacturer-specific data record in an advertising packet.
Here's a quite inefficient but conceptually simple way to convert the entire scanRecord to a hex string representation for debug printing:
String msg = "payload = ";
for (byte b : scanRecord)
msg += String.format("%02x ", b);
Note that this will include both the actual advertising packet and a number of meaningless trailing bytes, which should be ignored after parsing the structure (length field) contained in the advertising packet itself.

I had this same issue while developing my ble app, but after reading documentation on the following link: https://developer.android.com/reference/android/bluetooth/le/ScanResult.html
the important classes as far as UUID (Depending on the API you are developing for) is concerned are:
AdvertiseData
AdvertiseData.Builder
ScanRecord
ScanResult
after reading through documentation for these classes, this is the code I wrote to get UUID for any device being scanned:
//For API < 21:
private BluetoothAdapter.LeScanCallback scanCallBackLe =
new BluetoothAdapter.LeScanCallback() {
#Override
public void onLeScan(final BluetoothDevice device, int rssi, final byte[] scanRecord) {
final int RSSI = rssi;
if (RSSI >= signalThreshold){
scanHandler.post(new Runnable() {
#Override
public void run() {
AdvertiseData data = new AdvertiseData.Builder()
.addServiceUuid(ParcelUuid
.fromString(UUID
.nameUUIDFromBytes(scanRecord).toString())).build();
scannerActivity.addDevice(device, RSSI, getUUID(data));
}
});
}
}
};
//For APIs less than 21, Returns Device UUID
public String getUUID(AdvertiseData data){
List<ParcelUuid> UUIDs = data.getServiceUuids();
//ToastMakers.message(scannerActivity.getApplicationContext(), UUIDs.toString());
String UUIDx = UUIDs.get(0).getUuid().toString();
Log.e("UUID", " as list ->" + UUIDx);
return UUIDx;
}
For API's > 21:
private ScanCallback mScanCallback = new ScanCallback() {
#Override
public void onScanResult(int callbackType, final ScanResult result) {
Log.i("callbackType", String.valueOf(callbackType));
Log.i("result", result.toString());
final int RSSI = result.getRssi();
if (RSSI>=signalThreshold) {
scanHandler.post(
new Runnable() {
#Override
public void run() {
BluetoothDevice device = result.getDevice();
scannerActivity.addDevice(device, result.getRssi(), getUUID(result));
}
});
}
} ...}
//For APIs greater than 21, Returns Device UUID
public String getUUID(ScanResult result){
String UUIDx = UUID
.nameUUIDFromBytes(result.getScanRecord().getBytes()).toString();
ToastMakers.message(scannerActivity.getApplicationContext(), UUIDx);
Log.e("UUID", " as String ->>" + UUIDx);
return UUIDx;
}
I was able to obtain a 128 bit UUID of any device using this code. :)

I found a java library to parse the advertising packet
https://github.com/TakahikoKawasaki/nv-bluetooth

You can use standard android BluetoothGattCharacteristic apis like getFloatValue(int formatType, int offset), getIntValue(int formatType, int offset), getStringValue(int offset)..refer very nice android developer site tutorial here
if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {
int flag = characteristic.getProperties();
int format = -1;
if ((flag & 0x01) != 0) {
format = BluetoothGattCharacteristic.FORMAT_UINT16;
Log.d(TAG, "Heart rate format UINT16.");
} else {
format = BluetoothGattCharacteristic.FORMAT_UINT8;
Log.d(TAG, "Heart rate format UINT8.");
}
final int heartRate = characteristic.getIntValue(format, 1);
Log.d(TAG, "Received heart rate: " + heartRate);
intent.putExtra(EXTRA_DATA, String.valueOf(heartRate));
}

Related

Monitoring beacons that are advertising from my application through AltBeacon library

I'm working on a solution that advertises and scans in the iBeacon format using the AltBeacon library. The concern that i have is that the library scans all the devices which is fine but after parsing through the scanned devices it also tracks the advertising devices that are not advertising from my application. Is there anyway to solve this through using the library? If not what could be the alternate solution to this.
It is very important for me to track the advertising beacons that are only advertising from my application.
This is the code is use while advertising in iBeacon format through the AltBeacon library:
BluetoothManager bluetoothManager =
(BluetoothManager) applicationContext.getSystemService(Context.BLUETOOTH_SERVICE);
if (bluetoothManager != null) {
BluetoothAdapter mBluetoothAdapter = bluetoothManager.getAdapter();
BluetoothLeAdvertiser mBluetoothLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
if (mBluetoothLeAdvertiser != null) {
beacon = new Beacon.Builder()
.setId1(userId)
.setId2("1")
.setId3("1")
.setManufacturer(0x004C)
.setTxPower(-75)
.setDataFields(Arrays.asList(new Long[]{0l}))
.build();
beaconParser = new BeaconParser()
.setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24");
beaconTransmitter = new BeaconTransmitter(InventaSdk.getContext(), beaconParser);
beaconTransmitter.setBeacon(beacon);
}
}
Edit:
Parsing Beacon code:
/**
* Construct a Beacon from a Bluetooth LE packet collected by Android's Bluetooth APIs,
* including the raw Bluetooth device info
*
* #param scanData The actual packet bytes
* #param rssi The measured signal strength of the packet
* #param device The Bluetooth device that was detected
* #return An instance of a <code>Beacon</code>
*/
public Beacon fromScanData(byte[] scanData, int rssi, BluetoothDevice device) {
return fromScanData(scanData, rssi, device, new Beacon());
}
protected Beacon fromScanData(byte[] bytesToProcess, int rssi, BluetoothDevice device, Beacon beacon) {
BleAdvertisement advert = new BleAdvertisement(bytesToProcess);
boolean parseFailed = false;
Pdu pduToParse = null;
int startByte = 0;
ArrayList<Identifier> identifiers = new ArrayList<Identifier>();
ArrayList<Long> dataFields = new ArrayList<Long>();
for (Pdu pdu: advert.getPdus()) {
if (pdu.getType() == Pdu.GATT_SERVICE_UUID_PDU_TYPE ||
pdu.getType() == Pdu.MANUFACTURER_DATA_PDU_TYPE) {
pduToParse = pdu;
LogHelper.d(TAG, "Processing pdu type: "+pdu.getType()+bytesToHex(bytesToProcess)+" with startIndex: "+pdu.getStartIndex()+" endIndex: "+pdu.getEndIndex());
break;
}
else {
LogHelper.d(TAG, "Ignoring pdu type %02X "+ pdu.getType());
}
}
if (pduToParse == null) {
LogHelper.d(TAG, "No PDUs to process in this packet.");
parseFailed = true;
}
else {
byte[] serviceUuidBytes = null;
byte[] typeCodeBytes = longToByteArray(getMatchingBeaconTypeCode(), mMatchingBeaconTypeCodeEndOffset - mMatchingBeaconTypeCodeStartOffset + 1);
if (getServiceUuid() != null) {
serviceUuidBytes = longToByteArray(getServiceUuid(), mServiceUuidEndOffset - mServiceUuidStartOffset + 1, false);
}
startByte = pduToParse.getStartIndex();
boolean patternFound = false;
if (getServiceUuid() == null) {
if (byteArraysMatch(bytesToProcess, startByte + mMatchingBeaconTypeCodeStartOffset, typeCodeBytes)) {
patternFound = true;
}
} else {
if (byteArraysMatch(bytesToProcess, startByte + mServiceUuidStartOffset, serviceUuidBytes) &&
byteArraysMatch(bytesToProcess, startByte + mMatchingBeaconTypeCodeStartOffset, typeCodeBytes)) {
patternFound = true;
}
}
if (patternFound == false) {
// This is not a beacon
if (getServiceUuid() == null) {
LogHelper.d(TAG, "This is not a matching Beacon advertisement. (Was expecting "+byteArrayToString(typeCodeBytes)
+ ".The bytes I see are: "+
bytesToHex(bytesToProcess));
} else {
LogHelper.d(TAG, "This is not a matching Beacon advertisement. Was expecting "+
byteArrayToString(serviceUuidBytes)+
" at offset "+startByte + mServiceUuidStartOffset+"and "+byteArrayToString(typeCodeBytes)+
" at offset "+ startByte + mMatchingBeaconTypeCodeStartOffset + "The bytes I see are: "
+ bytesToHex(bytesToProcess));
}
parseFailed = true;
beacon = null;
} else {
LogHelper.d(TAG, "This is a recognized beacon advertisement -- "+
byteArrayToString(typeCodeBytes)+"seen");
LogHelper.d(TAG, "Bytes are: "+ bytesToHex(bytesToProcess));
}
if (patternFound) {
if (bytesToProcess.length <= startByte+mLayoutSize && mAllowPduOverflow) {
// If the layout size is bigger than this PDU, and we allow overflow. Make sure
// the byte buffer is big enough by zero padding the end so we don't try to read
// outside the byte array of the advertisement
LogHelper.d(TAG, "Expanding buffer because it is too short to parse: "+bytesToProcess.length+", needed: "+(startByte+mLayoutSize));
bytesToProcess = ensureMaxSize(bytesToProcess, startByte+mLayoutSize);
}
for (int i = 0; i < mIdentifierEndOffsets.size(); i++) {
int endIndex = mIdentifierEndOffsets.get(i) + startByte;
if (endIndex > pduToParse.getEndIndex() && mIdentifierVariableLengthFlags.get(i)) {
LogHelper.d(TAG, "Need to truncate identifier by "+(endIndex-pduToParse.getEndIndex()));
// If this is a variable length identifier, we truncate it to the size that
// is available in the packet
int start = mIdentifierStartOffsets.get(i) + startByte;
int end = pduToParse.getEndIndex()+1;
if (end <= start) {
LogHelper.d(TAG, "PDU is too short for identifer. Packet is malformed");
return null;
}
Identifier identifier = Identifier.fromBytes(bytesToProcess, start, end, mIdentifierLittleEndianFlags.get(i));
identifiers.add(identifier);
}
else if (endIndex > pduToParse.getEndIndex() && !mAllowPduOverflow) {
parseFailed = true;
LogHelper.d(TAG, "Cannot parse identifier "+i+" because PDU is too short. endIndex: " + endIndex + " PDU endIndex: " + pduToParse.getEndIndex());
}
else {
Identifier identifier = Identifier.fromBytes(bytesToProcess, mIdentifierStartOffsets.get(i) + startByte, endIndex+1, mIdentifierLittleEndianFlags.get(i));
identifiers.add(identifier);
}
}
for (int i = 0; i < mDataEndOffsets.size(); i++) {
int endIndex = mDataEndOffsets.get(i) + startByte;
if (endIndex > pduToParse.getEndIndex() && !mAllowPduOverflow) {
LogHelper.d(TAG, "Cannot parse data field "+i+" because PDU is too short. endIndex: " + endIndex + " PDU endIndex: " + pduToParse.getEndIndex()+". Setting value to 0");
dataFields.add(new Long(0l));
}
else {
String dataString = byteArrayToFormattedString(bytesToProcess, mDataStartOffsets.get(i) + startByte, endIndex, mDataLittleEndianFlags.get(i));
dataFields.add(Long.decode(dataString));
}
}
if (mPowerStartOffset != null) {
int endIndex = mPowerEndOffset + startByte;
int txPower = 0;
try {
if (endIndex > pduToParse.getEndIndex() && !mAllowPduOverflow) {
parseFailed = true;
LogHelper.d(TAG, "Cannot parse power field because PDU is too short. endIndex: " + endIndex + " PDU endIndex: " + pduToParse.getEndIndex());
}
else {
String powerString = byteArrayToFormattedString(bytesToProcess, mPowerStartOffset + startByte, mPowerEndOffset + startByte, false);
txPower = Integer.parseInt(powerString)+mDBmCorrection;
// make sure it is a signed integer
if (txPower > 127) {
txPower -= 256;
}
beacon.mTxPower = txPower;
}
}
catch (NumberFormatException e1) {
// keep default value
}
catch (NullPointerException e2) {
// keep default value
}
}
}
}
if (parseFailed) {
beacon = null;
}
else {
int beaconTypeCode = 0;
String beaconTypeString = byteArrayToFormattedString(bytesToProcess, mMatchingBeaconTypeCodeStartOffset+startByte, mMatchingBeaconTypeCodeEndOffset+startByte, false);
beaconTypeCode = Integer.parseInt(beaconTypeString);
// TODO: error handling needed on the parse
int manufacturer = 0;
String manufacturerString = byteArrayToFormattedString(bytesToProcess, startByte, startByte+1, true);
manufacturer = Integer.parseInt(manufacturerString);
String macAddress = null;
String name = null;
if (device != null) {
macAddress = device.getAddress();
name = device.getName();
}
beacon.mIdentifiers = identifiers;
beacon.mDataFields = dataFields;
beacon.mRssi = rssi;
beacon.mBeaconTypeCode = beaconTypeCode;
if (mServiceUuid != null) {
beacon.mServiceUuid = (int) mServiceUuid.longValue();
}
else {
beacon.mServiceUuid = -1;
}
beacon.mBluetoothAddress = macAddress;
beacon.mBluetoothName= name;
beacon.mManufacturer = manufacturer;
beacon.mParserIdentifier = mIdentifier;
beacon.mMultiFrameBeacon = extraParsers.size() > 0 || mExtraFrame;
}
return beacon;
}
Scan callbacks:
private ScanCallback getNewLeScanCallback() {
if (leScanCallback == null) {
leScanCallback = new ScanCallback() {
#MainThread
#Override
public void onScanResult(int callbackType, ScanResult scanResult) {
LogHelper.d(TAG, "got record");
List<ParcelUuid> uuids = scanResult.getScanRecord().getServiceUuids();
if (uuids != null) {
for (ParcelUuid uuid : uuids) {
LogHelper.d(TAG, "with service uuid: "+uuid);
}
}
try {
LogHelper.d("ScanRecord", "Raw Data: " + scanResult.toString());
LogHelper.d("ScanRecord", "Device Data Name: " + scanResult.getDevice().getName() + "Rssi: " + scanResult.getRssi() + "Address: " + scanResult.getDevice().getAddress() + "Service uuid: " + scanResult.getScanRecord().getServiceUuids());
}catch (Exception e){
LogHelper.d("ScanRecord",e.getMessage());
e.printStackTrace();
}
mCycledLeScanCallback.onLeScan(scanResult.getDevice(),
scanResult.getRssi(), scanResult.getScanRecord().getBytes());
if (mBackgroundLScanStartTime > 0) {
LogHelper.d(TAG, "got a filtered scan result in the background.");
}
}
#MainThread
#Override
public void onBatchScanResults(List<ScanResult> results) {
LogHelper.d(TAG, "got batch records");
for (ScanResult scanResult : results) {
mCycledLeScanCallback.onLeScan(scanResult.getDevice(),
scanResult.getRssi(), scanResult.getScanRecord().getBytes());
}
if (mBackgroundLScanStartTime > 0) {
LogHelper.d(TAG, "got a filtered batch scan result in the background.");
}
}
#MainThread
#Override
public void onScanFailed(int errorCode) {
Intent intent = new Intent("onScanFailed");
intent.putExtra("errorCode", errorCode);
LocalBroadcastManager.getInstance(CycledLeScannerForLollipop.this.mContext).sendBroadcast(intent);
switch (errorCode) {
case SCAN_FAILED_ALREADY_STARTED:
LogHelper.e(TAG, "Scan failed: a BLE scan with the same settings is already started by the app");
break;
case SCAN_FAILED_APPLICATION_REGISTRATION_FAILED:
LogHelper.e(TAG, "Scan failed: app cannot be registered");
break;
case SCAN_FAILED_FEATURE_UNSUPPORTED:
LogHelper.e(TAG, "Scan failed: power optimized scan feature is not supported");
break;
case SCAN_FAILED_INTERNAL_ERROR:
LogHelper.e(TAG, "Scan failed: internal error");
break;
default:
LogHelper.e(TAG, "Scan failed with unknown error (errorCode=" + errorCode + ")");
break;
}
}
};
}
return leScanCallback;
}
The general approach to filter for “your” beacons is to see an an identifier prefix that is common to all your beacons. You then tell if it is your beacon by filtering on beacons that match this identifier prefix.
Two ways to do the filtering:
A) Software filtering after scan results come in.
With this approach, you wait until you parse the beacons and then use an if statement to see if the beacon identifiers match your prefix. If not, do not process it. The Android Beacon Library has this as a built-in feature by using Region objects to provide matching patterns for “your” beacons.
// replace uuid with your own
beaconManager.startRangingBeaconsInRegion(new Region("matchOnlyMyBeacons", Identifier.parse(“2F234454-CF6D-4A0F-ADF2-F4911BA9”)), null, null));
beaconManager.addRangeNotifier(new RangeNotifier() {
#Override
public void didRangeBeaconsInRegion(Collection<Beacon> beacons, Region region) {
// only beacons matching the identifiers in the Region are included here
}
});
Since you are mot using the library as a whole but copying some of its code, you may have to build similar logic yourself like this:
// replace the uuid with yours below
if (beacon.getID1().equals(Identifier.parse(“2F234454-CF6D-4A0F-ADF2-F4911BA9”)){
// only process matching beacons here
}
This is a simple approach as very flexible. It works well in cases where your app runs only in the foreground or in the background when usually there are few BLE devices around that are not of interest.
The disadvantage is that it can burn cpu and battery if lots of beacons are around that are not of interest.
B) Use hardware scan filters
Android 6+ APIs allow you to put similar matching functions into the Bluetooth chip itself so all scan callbacks you get already match the identifier prefix. This is us less taxing on CPU and battery but has disadvantages:
Not all devices support this, though most devices built since 2018 do.
Hardware filters are a limited resource. If other apps take all of them up, you will not get scan results.
Filters are inflexible. If even a single byte of the advertisement prefix doesn’t match (commonly due to a differing manufacturer code) you will not get a scan result.
ScanFilter.Builder builder = new ScanFilter.Builder();
builder.setServiceUuid(null);
byte[] filterBytes = new byte[]{
/* 0215 are the start of iBeacon. Use beac for AltBeacon */
(byte) 0x02, (byte) 0x15,
// These bytes are your 16 byte proximityUUID (ID1)
(byte) 0x2F, (byte) 0x23, (byte) 0x44, (byte) 0x54, (byte) 0xCF, (byte) 0x6D, (byte) 0x4A, (byte) 0x0F, (byte) 0xAD, (byte) 0xF2, (byte) 0xF4, (byte) 0x91, (byte) 0x1B, (byte) 0xA9, (byte) 0xFF, (byte) 0xA6
};
byte[] maskBytes = new byte[]{
/* Make this the same length as your filter bytes, and set every value to 0xff to match all bytes */
(byte) 0xff, (byte) 0xff,
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff
};
builder.setManufacturerData((int) 0x004c /* apple for iBeacon, use 0x0118 for AltBeacon */, filterBytes, maskBytes);
ScanFilter[] scanFilters = new ScanFilter[] { builder.build() };
scanner.startScan(scanFilters, scanSettings, scanCallback);

Missing value for RR-interval (BLE / Polar device)

I'm owning a Polar H10 device and I'm interested in the heart rate as well as RR-interval which I read out with the official bluetooth low energy API of Android. The Polar device sends every every second a package with the heart rate and the RR-interval. Now I have recognized that in every such package is a heart rate value but in some packages there are no RR-interval values (The value of the RR-interval is -1).
Why does this happen? Is my device broken or did I made a mistake in the implementation or does somebody else also face this issue?
Edit: Here is the code. In the method public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) I'm receiving changed values from the Polar Device. This method is triggered approximately every second. Then I parse the characteristic as follows:
public int[] parse(BluetoothGattCharacteristic characteristic) {
double heartRate = extractHeartRate(c);
Integer[] interval = extractBeatToBeatInterval(c);
int[] result = null;
if (interval != null) {
result = new int[interval.length + 1];
} else {
result = new int[2];
result[1] = -1;
}
result[0] = (int) heartRate;
if (interval != null) {
for (int i = 0; i < interval.length; i++) {
result[i+1] = interval[i];
}
}
return result;
}
private static double extractHeartRate(
BluetoothGattCharacteristic characteristic) {
int flag = characteristic.getProperties();
Log.d(TAG, "Heart rate flag: " + flag);
int format = -1;
// Heart rate bit number format
if ((flag & 0x01) != 0) {
format = BluetoothGattCharacteristic.FORMAT_UINT16;
Log.d(TAG, "Heart rate format UINT16.");
} else {
format = BluetoothGattCharacteristic.FORMAT_UINT8;
Log.d(TAG, "Heart rate format UINT8.");
}
final int heartRate = characteristic.getIntValue(format, 1);
Log.d(TAG, String.format("Received heart rate: %d", heartRate));
return heartRate;
}
private static Integer[] extractBeatToBeatInterval(
BluetoothGattCharacteristic characteristic) {
int flag = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0);
int format = -1;
int energy = -1;
int offset = 1; // This depends on hear rate value format and if there is energy data
int rr_count = 0;
if ((flag & 0x01) != 0) {
format = BluetoothGattCharacteristic.FORMAT_UINT16;
Log.d(TAG, "Heart rate format UINT16.");
offset = 3;
} else {
format = BluetoothGattCharacteristic.FORMAT_UINT8;
Log.d(TAG, "Heart rate format UINT8.");
offset = 2;
}
if ((flag & 0x08) != 0) {
// calories present
energy = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset);
offset += 2;
Log.d(TAG, "Received energy: {}"+ energy);
}
if ((flag & 0x16) != 0){
// RR stuff.
Log.d(TAG, "RR stuff found at offset: "+ offset);
Log.d(TAG, "RR length: "+ (characteristic.getValue()).length);
rr_count = ((characteristic.getValue()).length - offset) / 2;
Log.d(TAG, "RR length: "+ (characteristic.getValue()).length);
Log.d(TAG, "rr_count: "+ rr_count);
if (rr_count > 0) {
Integer[] mRr_values = new Integer[rr_count];
for (int i = 0; i < rr_count; i++) {
mRr_values[i] = characteristic.getIntValue(
BluetoothGattCharacteristic.FORMAT_UINT16, offset);
offset += 2;
Log.d(TAG, "Received RR: " + mRr_values[i]);
}
return mRr_values;
}
}
Log.d(TAG, "No RR data on this update: ");
return null;
}
The first element returned by the parse method is the heart rate and the second element is the RR-interval. It happens that sometimes the second element is -1 (i.e. no RR-interval detected).
There is nothing wrong with your Polar device or the software you posted.
The RR-interval measure may be missing from some transmitted packets and the if ((flag & 0x16) != 0) accounts for this case.
Suppose for example that your device send a heart measure every second and you have 50 beats/sec: there will be some intervals where the RR interval is not measured because there isnt a detected beat in that second (it is a simplified explanation, just to get the point).

How to write “0xFF” as a characteristics value in android BLE?

I am trying to write hex value 0xFF in the fragrance dispenser device using BluetoothGattCharacteristic method setValue(..) .I do get success status code 0 in the call back method onCharacteristicWrite() But device does not perform any action, ideally it should emit fragrance.
below is my sample code to write to the characteristics
private void writeCharacteristic(CallbackContext callbackContext, UUID serviceUUID, UUID characteristicUUID, byte[] data, int writeType) {
boolean success = false;
if (gatt == null) {
callbackContext.error("BluetoothGatt is null");
return;
}
BluetoothGattService service = gatt.getService(serviceUUID);
BluetoothGattCharacteristic characteristic = findWritableCharacteristic(service, characteristicUUID, writeType);
if (characteristic == null) {
callbackContext.error("Characteristic " + characteristicUUID + " not found.");
} else {
int data2=0xFF;
characteristic.setValue(data2, BluetoothGattCharacteristic.FORMAT_UINT16, 0);
characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
writeCallback = callbackContext;
if (gatt.writeCharacteristic(characteristic)) {
success = true;
System.out.println(" writeCharacteristic success");
} else {
writeCallback = null;
callbackContext.error("Write failed");
}
}
Please suggest way to write hex data in setValue() method of BluetoothGattCharacteristic .
Thanks
0xFF in
BluetoothGattCharacteristic.FORMAT_UINT16 means you'll send FF 00 because you set it to send a 16 bit unsigned number. To send only 0xFF (and I don't know if that makes a difference) you'll have to set the format to UINT8.
characteristic.setValue(data2, BluetoothGattCharacteristic.FORMAT_UINT8, 0);
You can send byte array to charcteristics.
Convert your hex to byte array using below method.link
public static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
convert your number to hex first ...
public static String toHex(String arg)
{
try
{
return String.format("%01x", new BigInteger(1, arg.getBytes("UTF-8")));
}
catch (UnsupportedEncodingException e)
{
e.printStackTrace();
return "";
}
}
//set data
characteristic.setValue(hexStringToByteArray(toHex(255+""));

Retrieving Android BluetoothLE device information (Major / minor / identifier / ProximityUUID) on scan?

I've been looking around and unfortunately the android ibeacon library has been deprecated, so I am attempting to do this native. I have implemented the BluetoothAdapter.LeScanCallback and the built in onLeScan() method that will fire when a device is picked up. I would like to read in that device's ProximityUUID, major and minor characteristics and identifier. I'm not sure how to get that information out of the Android object BluetoothDevice.
How do I extract that information (ProximityUUID, major, minor, & identifier characteristics) from the Android BluetoothDevice, or is there another way to do it?
Thanks!
you can refer this post to fully understand what those bytes means in LeScanCallback .
And this is my code to parse all information needed:
// an object with all information embedded from LeScanCallback data
public class ScannedBleDevice implements Serializable {
// public BluetoothDevice BLEDevice;
/**
* Returns the hardware address of this BluetoothDevice.
* <p>
* For example, "00:11:22:AA:BB:CC".
*
* #return Bluetooth hardware address as string
*/
public String MacAddress;
public String DeviceName;
public double RSSI;
public double Distance;
public byte[] CompanyId;
public byte[] IbeaconProximityUUID;
public byte[] Major;
public byte[] Minor;
public byte Tx;
public long ScannedTime;
}
// use this method to parse those bytes and turn to an object which defined proceeding.
// the uuidMatcher works as a UUID filter, put null if you want parse any BLE advertising data around.
private ScannedBleDevice ParseRawScanRecord(BluetoothDevice device,
int rssi, byte[] advertisedData, byte[] uuidMatcher) {
try {
ScannedBleDevice parsedObj = new ScannedBleDevice();
// parsedObj.BLEDevice = device;
parsedObj.DeviceName = device.getName();
parsedObj.MacAddress = device.getAddress();
parsedObj.RSSI = rssi;
List<UUID> uuids = new ArrayList<UUID>();
int skippedByteCount = advertisedData[0];
int magicStartIndex = skippedByteCount + 1;
int magicEndIndex = magicStartIndex
+ advertisedData[magicStartIndex] + 1;
ArrayList<Byte> magic = new ArrayList<Byte>();
for (int i = magicStartIndex; i < magicEndIndex; i++) {
magic.add(advertisedData[i]);
}
byte[] companyId = new byte[2];
companyId[0] = magic.get(2);
companyId[1] = magic.get(3);
parsedObj.CompanyId = companyId;
byte[] ibeaconProximityUUID = new byte[16];
for (int i = 0; i < 16; i++) {
ibeaconProximityUUID[i] = magic.get(i + 6);
}
if (uuidMatcher != null) {
if (ibeaconProximityUUID.length != uuidMatcher.length) {
Log.e(LOG_TAG,
"Scanned UUID: "
+ Util.BytesToHexString(
ibeaconProximityUUID, " ")
+ " filtered by UUID Matcher "
+ Util.BytesToHexString(uuidMatcher, " ")
+ " with length requirment.");
return null;
}
for (int i = 0; i < 16; i++) {
if (ibeaconProximityUUID[i] != uuidMatcher[i]) {
Log.e(LOG_TAG,
"Scanned UUID: "
+ Util.BytesToHexString(
ibeaconProximityUUID, " ")
+ " filtered by UUID Matcher "
+ Util.BytesToHexString(uuidMatcher,
" "));
return null;
}
}
}
parsedObj.IbeaconProximityUUID = ibeaconProximityUUID;
byte[] major = new byte[2];
major[0] = magic.get(22);
major[1] = magic.get(23);
parsedObj.Major = major;
byte[] minor = new byte[2];
minor[0] = magic.get(24);
minor[1] = magic.get(25);
parsedObj.Minor = minor;
byte tx = 0;
tx = magic.get(26);
parsedObj.Tx = tx;
parsedObj.ScannedTime = new Date().getTime();
return parsedObj;
} catch (Exception ex) {
Log.e(LOG_TAG, "skip one unknow format data...");
// Log.e(LOG_TAG,
// "Exception in ParseRawScanRecord with advertisedData: "
// + Util.BytesToHexString(advertisedData, " ")
// + ", detail: " + ex.getMessage());
return null;
}
}
Payloads of advertising packets should be parsed as a list of AD structures.
iBeacon is a kind of AD structures.
See "iBeacon as a kind of AD structures" for details. Also, see an answer to a similar question.

Beacon discovery using android code? [duplicate]

Im trying to get UUID of ble device. I was following android developers guide and so far I can get only device name and rssi. Im trying to get Uuid of the device that comes to scanning method that looks like this:
public void onLeScan(final BluetoothDevice device, int rssi,byte[] scanRecord) {
ParcelUuid[] myUUid =device.getUuids();
for(ParcelUuid a :myUUid){
Log.d("UUID",a.getUuid().toString());
}
String s = new String(scanRecord);
int len = scanRecord.length;
String scanRecords =new String(scanRecord) ;
deviceMap.put(device.getName().toString(), rssi);
Message msg = MainActivity.myHandler.obtainMessage();
Bundle bundle = new Bundle();
bundle.putCharSequence("dev_name", device.getName().toString());
bundle.putCharSequence("rssi", Integer.toString(rssi));
msg.setData(bundle);
MainActivity.myHandler.sendMessage(msg);
}
this returns - btif_gattc_upstreams_evt: Event 4096
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.
As mentioned in comments, a BLE device doesn't really have a specific UUID (but rather many for included services). However, some schemes such as iBeacon encode a unique identifier in a manufacturer-specific data record in an advertising packet.
Here's a quite inefficient but conceptually simple way to convert the entire scanRecord to a hex string representation for debug printing:
String msg = "payload = ";
for (byte b : scanRecord)
msg += String.format("%02x ", b);
Note that this will include both the actual advertising packet and a number of meaningless trailing bytes, which should be ignored after parsing the structure (length field) contained in the advertising packet itself.
I had this same issue while developing my ble app, but after reading documentation on the following link: https://developer.android.com/reference/android/bluetooth/le/ScanResult.html
the important classes as far as UUID (Depending on the API you are developing for) is concerned are:
AdvertiseData
AdvertiseData.Builder
ScanRecord
ScanResult
after reading through documentation for these classes, this is the code I wrote to get UUID for any device being scanned:
//For API < 21:
private BluetoothAdapter.LeScanCallback scanCallBackLe =
new BluetoothAdapter.LeScanCallback() {
#Override
public void onLeScan(final BluetoothDevice device, int rssi, final byte[] scanRecord) {
final int RSSI = rssi;
if (RSSI >= signalThreshold){
scanHandler.post(new Runnable() {
#Override
public void run() {
AdvertiseData data = new AdvertiseData.Builder()
.addServiceUuid(ParcelUuid
.fromString(UUID
.nameUUIDFromBytes(scanRecord).toString())).build();
scannerActivity.addDevice(device, RSSI, getUUID(data));
}
});
}
}
};
//For APIs less than 21, Returns Device UUID
public String getUUID(AdvertiseData data){
List<ParcelUuid> UUIDs = data.getServiceUuids();
//ToastMakers.message(scannerActivity.getApplicationContext(), UUIDs.toString());
String UUIDx = UUIDs.get(0).getUuid().toString();
Log.e("UUID", " as list ->" + UUIDx);
return UUIDx;
}
For API's > 21:
private ScanCallback mScanCallback = new ScanCallback() {
#Override
public void onScanResult(int callbackType, final ScanResult result) {
Log.i("callbackType", String.valueOf(callbackType));
Log.i("result", result.toString());
final int RSSI = result.getRssi();
if (RSSI>=signalThreshold) {
scanHandler.post(
new Runnable() {
#Override
public void run() {
BluetoothDevice device = result.getDevice();
scannerActivity.addDevice(device, result.getRssi(), getUUID(result));
}
});
}
} ...}
//For APIs greater than 21, Returns Device UUID
public String getUUID(ScanResult result){
String UUIDx = UUID
.nameUUIDFromBytes(result.getScanRecord().getBytes()).toString();
ToastMakers.message(scannerActivity.getApplicationContext(), UUIDx);
Log.e("UUID", " as String ->>" + UUIDx);
return UUIDx;
}
I was able to obtain a 128 bit UUID of any device using this code. :)
I found a java library to parse the advertising packet
https://github.com/TakahikoKawasaki/nv-bluetooth
You can use standard android BluetoothGattCharacteristic apis like getFloatValue(int formatType, int offset), getIntValue(int formatType, int offset), getStringValue(int offset)..refer very nice android developer site tutorial here
if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {
int flag = characteristic.getProperties();
int format = -1;
if ((flag & 0x01) != 0) {
format = BluetoothGattCharacteristic.FORMAT_UINT16;
Log.d(TAG, "Heart rate format UINT16.");
} else {
format = BluetoothGattCharacteristic.FORMAT_UINT8;
Log.d(TAG, "Heart rate format UINT8.");
}
final int heartRate = characteristic.getIntValue(format, 1);
Log.d(TAG, "Received heart rate: " + heartRate);
intent.putExtra(EXTRA_DATA, String.valueOf(heartRate));
}

Categories

Resources