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).
Related
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);
I am developing an app that requires real time sensor data streaming from a wearable. I am using an LG G watch and the accelerometer and gyroscope sensors.
The sensor data and system current time are packaged as an array of 4 bytes for each axis of each sensor X 32 samples (hence 4 X 3 X COUNT.
This data is then sent through the message api.
Main question:
I have registered the sensors with fastest update rate (SENSOR_DELAY_FASTEST) and when I analyze the data samples I have captured with the mobile device, the time step is roughly 200ms per sample (roughly 5 HZ).
I am expecting much faster.
Am I limited by:
1) bluetooth 4.0 bandwith
2) LG G watch sensor (it's an Invensense and the bandwidth should be much higher)... or
3) the message API
Would DataApi allow me to stream data to the mobile device much faster?
Here is a section of my code:
static final int COUNT = 32;
static ByteBuffer AcceleratorBuffer = ByteBuffer.allocate((4 * 3 * COUNT) + (8 * 1 * COUNT));
static ByteBuffer GyroBuffer = ByteBuffer.allocate((4 * 3 * COUNT) + (8 * 1 * COUNT));
static int accelerator_cycle = 0;
static int gyro_cycle = 0;
static AcceleratorWearSensorListener mAcceleratorSensorEventListener = null;
static GyroWearSensorListener mGyroSensorEventListener = null;
static class GyroWearSensorListener implements SensorEventListener {
GoogleApiClient mGoogleApiClient;
public GyroWearSensorListener(GoogleApiClient mGoogleApiClient) {
this.mGoogleApiClient = mGoogleApiClient;
}
#Override
public void onSensorChanged(SensorEvent event) {
if (On) {
Float x = event.values[0];
Float y = event.values[1];
Float z = event.values[2];
Log.i(TAG, "Gyro x: " + x + " y: " + y + " z: " + z);
GyroBuffer.putFloat(x).putFloat(y).putFloat(z).putLong(System.currentTimeMillis()).array();
++gyro_cycle;
if (gyro_cycle % COUNT == 0) {
Wearable.NodeApi.getConnectedNodes(mGoogleApiClient)
.setResultCallback(new ResultCallback<NodeApi.GetConnectedNodesResult>() {
#Override
public void onResult(NodeApi.GetConnectedNodesResult nodes) {
Log.i(TAG, "Nodes size : " + nodes.getNodes().size());
for (Node node : nodes.getNodes()) {
if (node != null && mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
Wearable.MessageApi.sendMessage(
mGoogleApiClient, node.getId(), GYROSCOPE_DATA_PACKET, GyroBuffer.array())
.setResultCallback(
new ResultCallback<MessageApi.SendMessageResult>() {
#Override
public void onResult(MessageApi.SendMessageResult sendMessageResult) {
if (!sendMessageResult.getStatus().isSuccess()) {
Log.e(TAG, "Failed to send message with status code: "
+ sendMessageResult.getStatus().getStatusCode());
} else {
Log.e(TAG, "send gyro data successfully ");
}
}
}
);
} else {
Log.e(TAG, "node null or client null or not connected. ");
}
}
}
});
GyroBuffer.clear();
gyro_cycle = 0;
}
}
}
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));
}
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));
}
i'm developing an android application uses bluetooth LE, i need to get RR interval. I start form this example on android developer site:
private void broadcastUpdate(final String action,
final BluetoothGattCharacteristic characteristic) {
final Intent intent = new Intent(action);
// This is special handling for the Heart Rate Measurement profile. Data
// parsing is carried out as per profile specifications.
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, String.format("Received heart rate: %d", heartRate));
intent.putExtra(EXTRA_DATA, String.valueOf(heartRate));
} else {
// For all other profiles, writes the data formatted in HEX.
final byte[] data = characteristic.getValue();
if (data != null && data.length > 0) {
final StringBuilder stringBuilder = new StringBuilder(data.length);
for(byte byteChar : data)
stringBuilder.append(String.format("%02X ", byteChar));
intent.putExtra(EXTRA_DATA, new String(data) + "\n" +
stringBuilder.toString());
}
}
sendBroadcast(intent);
}
How i can get RR interval? I just do it with iOS, but on java i don't know how to do..
this is my code on iOS, and works perfectly:
- (void) updateWithHRMData:(NSData *)datas {
const uint8_t *reportData = [datas bytes];
uint16_t bpm = 0;
uint16_t bpm2 = 0;
if ((reportData[0] & 0x04) == 0)
{
NSLog(#"%#", #"Data are not present");
}
else
{
bpm = CFSwapInt16LittleToHost(*(uint16_t *)(&reportData[2]));
bpm2 = CFSwapInt16LittleToHost(*(uint16_t *)(&reportData[4]));
if (bpm != 0 || bpm2 != 0) {
self.deviceReady = true;
[lblNofascia setAlpha:0.0];
[btnMonitor setAlpha:1.0];
if (isRunning) {
[self.elencoBattiti addObject:[NSString stringWithFormat:#"%u", bpm]];
NSLog(#"%u", bpm);
if (bpm2 != 0) {
[self.elencoBattiti addObject:[NSString stringWithFormat:#"%u", bpm2]];
NSLog(#"%u", bpm2);
}
}
} else {
if (isRunning) {
totErrori++;
NSLog(#"Dato non trasmesso");
if (totErrori > 5) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Attenzione" message:#"Ho perso la connettività con la fascia. Ripetere la misurazione" delegate:self cancelButtonTitle:nil otherButtonTitles:#"Continua", nil];
[alert show];
[self stopRunning];
}
}
}
}
}
thank you
I have the following.
if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {
int flag = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0);
int format = -1;
int energy = -1;
int offset = 1;
int rr_count = 0;
if ((flag & 0x01) != 0) {
format = BluetoothGattCharacteristic.FORMAT_UINT16;
Logger.trace("Heart rate format UINT16.");
offset = 3;
} else {
format = BluetoothGattCharacteristic.FORMAT_UINT8;
Logger.trace("Heart rate format UINT8.");
offset = 2;
}
final int heartRate = characteristic.getIntValue(format, 1);
Logger.trace("Received heart rate: {}", heartRate);
if ((flag & 0x08) != 0) {
// calories present
energy = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset);
offset += 2;
Logger.trace("Received energy: {}", energy);
}
if ((flag & 0x10) != 0){
// RR stuff.
rr_count = ((characteristic.getValue()).length - offset) / 2;
for (int i = 0; i < rr_count; i++){
mRr_values[i] = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset);
offset += 2;
Logger.trace("Received RR: {}", mRr_values[i]);
}
}
Thanks Ifor. With some fixes the android code should work. The bit mask for RR data flag should be 16 according to specs and the array should be declared:
if ((flag & 0x16) != 0){
// RR stuff.
rr_count = ((characteristic.getValue()).length - offset) / 2;
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;
Logger.trace("Received RR: {}", mRr_values[i]);
}
}