Working on exposing the iBeacon Manufacturer specific data
This commit is contained in:
@@ -5,8 +5,10 @@ import java.util.Collection;
|
|||||||
import uk.co.alt236.btlescan.R;
|
import uk.co.alt236.btlescan.R;
|
||||||
import uk.co.alt236.btlescan.containers.AdRecord;
|
import uk.co.alt236.btlescan.containers.AdRecord;
|
||||||
import uk.co.alt236.btlescan.containers.BluetoothLeDevice;
|
import uk.co.alt236.btlescan.containers.BluetoothLeDevice;
|
||||||
|
import uk.co.alt236.btlescan.containers.ManufacturerDataIBeacon;
|
||||||
import uk.co.alt236.btlescan.util.AdRecordUtils;
|
import uk.co.alt236.btlescan.util.AdRecordUtils;
|
||||||
import uk.co.alt236.btlescan.util.ByteUtils;
|
import uk.co.alt236.btlescan.util.ByteUtils;
|
||||||
|
import uk.co.alt236.btlescan.util.IBeaconUtils;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
@@ -61,11 +63,38 @@ public class DetailsActivity extends Activity{
|
|||||||
append(sb, AdRecordUtils.getRecordDataAsString(record), null);
|
append(sb, AdRecordUtils.getRecordDataAsString(record), null);
|
||||||
append(sb, "", null);
|
append(sb, "", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
append(sb, "Additional", null);
|
||||||
|
append(sb, "-----------------", null);
|
||||||
|
final boolean isIBeacon = IBeaconUtils.isThisAnIBeacon(device);
|
||||||
|
append(sb, "Is iBeacon", isIBeacon);
|
||||||
|
|
||||||
|
if(isIBeacon){
|
||||||
|
final ManufacturerDataIBeacon iBeaconData = new ManufacturerDataIBeacon(device);
|
||||||
|
append(sb, "Company ID", iBeaconData.getCompanyIdentifier());
|
||||||
|
append(sb, "iBeacon Advertisment", iBeaconData.getIBeaconAdvertisement());
|
||||||
|
append(sb, "UUID", iBeaconData.getUUID());
|
||||||
|
append(sb, "Major", iBeaconData.getMajor());
|
||||||
|
append(sb, "Minor", iBeaconData.getMinor());
|
||||||
|
append(sb, "TX Power", iBeaconData.getCalibratedTxPower());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mTvDetails.setText(sb.toString());
|
mTvDetails.setText(sb.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void append(StringBuilder sb, String label, boolean value) {
|
||||||
|
append(sb, label, String.valueOf(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void append(StringBuilder sb, String label, int value) {
|
||||||
|
append(sb, label, String.valueOf(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void append(StringBuilder sb, String label, long value) {
|
||||||
|
append(sb, label, String.valueOf(value));
|
||||||
|
}
|
||||||
|
|
||||||
private static void append(StringBuilder sb, byte[] value){
|
private static void append(StringBuilder sb, byte[] value){
|
||||||
append(sb, ByteUtils.byteArrayToHexString(value), null);
|
append(sb, ByteUtils.byteArrayToHexString(value), null);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,41 +1,88 @@
|
|||||||
package uk.co.alt236.btlescan.containers;
|
package uk.co.alt236.btlescan.containers;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import uk.co.alt236.btlescan.util.ByteUtils;
|
||||||
|
|
||||||
public final class ManufacturerDataIBeacon {
|
public final class ManufacturerDataIBeacon {
|
||||||
// 0 FF # Manufacturer specific data AD type
|
// 0 4C # Byte 1 (LSB) of Company identifier code
|
||||||
// 1 4C # Byte 1 of Company identifier code
|
// 1 00 # Byte 0 (MSB) of Company identifier code (0x004C == Apple)
|
||||||
// 2 00 # Byte 0 of Company identifier code (0x004C == Apple)
|
// 2 02 # Byte 0 of iBeacon advertisement indicator
|
||||||
// 3 02 # Byte 0 of iBeacon advertisement indicator
|
// 3 15 # Byte 1 of iBeacon advertisement indicator
|
||||||
// 4 15 # Byte 1 of iBeacon advertisement indicator
|
// 4 e2 |\
|
||||||
// 5 e2 #
|
// 5 c5 |\\
|
||||||
// 6 c5 ##
|
// 6 6d |#\\
|
||||||
// 7 6d ###
|
// 7 b5 |##\\
|
||||||
// 8 b5 ####
|
// 8 df |###\\
|
||||||
// 9 df #####
|
// 9 fb |####\\
|
||||||
// 10 fb ######
|
// 10 48 |#####\\
|
||||||
// 11 48 #######
|
// 11 d2 |#####|| iBeacon proximity UUID
|
||||||
// 12 d2 ######## iBeacon proximity uuid
|
// 12 b0 |#####||
|
||||||
// 13 b0 ########
|
// 13 60 |#####//
|
||||||
// 14 60 #######
|
// 14 d0 |####//
|
||||||
// 15 d0 ######
|
// 15 f5 |###//
|
||||||
// 16 f5 #####
|
// 16 a7 |##//
|
||||||
// 17 a7 ####
|
// 17 10 |#//
|
||||||
// 18 10 ###
|
// 18 96 |//
|
||||||
// 19 96 ##
|
// 19 e0 |/
|
||||||
// 20 e0 #
|
// 20 00 # major
|
||||||
// 21 00
|
// 21 00
|
||||||
// 22 00 # major
|
// 22 00# minor
|
||||||
// 23 00
|
// 23 00
|
||||||
// 24 00 # minor
|
// 24 c5 # The 2's complement of the calibrated Tx Power
|
||||||
// 25 c5 # The 2's complement of the calibrated Tx Power
|
|
||||||
|
|
||||||
private final byte[] mData;
|
private final byte[] mData;
|
||||||
private final int mTxPower;
|
private final int mCalibratedTxPower;
|
||||||
|
private final int mCompanyIdentidier;
|
||||||
|
private final int mIBeaconAdvertisment;
|
||||||
|
private final int mMajor;
|
||||||
|
private final int mMinor;
|
||||||
|
private final long mUUID;
|
||||||
|
|
||||||
|
public ManufacturerDataIBeacon(BluetoothLeDevice device){
|
||||||
|
this(device.getAdRecordStore().getRecord(AdRecord.TYPE_MANUFACTURER_SPECIFIC_DATA).getData());
|
||||||
|
}
|
||||||
|
|
||||||
public ManufacturerDataIBeacon(byte[] data){
|
public ManufacturerDataIBeacon(byte[] data){
|
||||||
mData = data;
|
mData = data;
|
||||||
mTxPower = 0;
|
mCompanyIdentidier = convert2ByteArrayToInt(Arrays.copyOfRange(mData, 0, 2));
|
||||||
|
mIBeaconAdvertisment = convert2ByteArrayToInt(Arrays.copyOfRange(mData, 2, 2));
|
||||||
|
mUUID = ByteUtils.getLongFromByteArray(Arrays.copyOfRange(mData, 4, 16));
|
||||||
|
mMajor = convert2ByteArrayToInt(Arrays.copyOfRange(mData, 20, 2));
|
||||||
|
mMinor = convert2ByteArrayToInt(Arrays.copyOfRange(mData, 22, 2));
|
||||||
|
mCalibratedTxPower = ByteUtils.getIntFromByte(data[24]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int convert2ByteArrayToInt(byte[] array){
|
||||||
|
final byte[] result = new byte[4];
|
||||||
|
result[2] = array[0];
|
||||||
|
result[3] = array[1];
|
||||||
|
return ByteUtils.getIntFromByteArray(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCalibratedTxPower(){
|
||||||
|
return mCalibratedTxPower;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCompanyIdentifier(){
|
||||||
|
return mCompanyIdentidier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getIBeaconAdvertisement(){
|
||||||
|
return mIBeaconAdvertisment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMajor(){
|
||||||
|
return mMajor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMinor(){
|
||||||
|
return mMinor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getUUID(){
|
||||||
|
return mUUID;
|
||||||
|
}
|
||||||
|
|
||||||
// Code taken from: http://stackoverflow.com/questions/20416218/understanding-ibeacon-distancing
|
// Code taken from: http://stackoverflow.com/questions/20416218/understanding-ibeacon-distancing
|
||||||
protected static double calculateAccuracy(int txPower, double rssi) {
|
protected static double calculateAccuracy(int txPower, double rssi) {
|
||||||
@@ -48,7 +95,7 @@ public final class ManufacturerDataIBeacon {
|
|||||||
return Math.pow(ratio,10);
|
return Math.pow(ratio,10);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
double accuracy = (0.89976)*Math.pow(ratio,7.7095) + 0.111;
|
final double accuracy = (0.89976)*Math.pow(ratio,7.7095) + 0.111;
|
||||||
return accuracy;
|
return accuracy;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ public class AdRecordUtils {
|
|||||||
//Done once we run out of records
|
//Done once we run out of records
|
||||||
if (length == 0) break;
|
if (length == 0) break;
|
||||||
|
|
||||||
final int type = ByteUtils.convertByteToInt(scanRecord[index]);
|
final int type = ByteUtils.getIntFromByte(scanRecord[index]);
|
||||||
|
|
||||||
//Done if our record isn't a valid type
|
//Done if our record isn't a valid type
|
||||||
if (type == 0) break;
|
if (type == 0) break;
|
||||||
@@ -77,7 +77,7 @@ public class AdRecordUtils {
|
|||||||
//Done once we run out of records
|
//Done once we run out of records
|
||||||
if (length == 0) break;
|
if (length == 0) break;
|
||||||
|
|
||||||
final int type = ByteUtils.convertByteToInt(scanRecord[index]);
|
final int type = ByteUtils.getIntFromByte(scanRecord[index]);
|
||||||
|
|
||||||
//Done if our record isn't a valid type
|
//Done if our record isn't a valid type
|
||||||
if (type == 0) break;
|
if (type == 0) break;
|
||||||
@@ -102,7 +102,7 @@ public class AdRecordUtils {
|
|||||||
//Done once we run out of records
|
//Done once we run out of records
|
||||||
if (length == 0) break;
|
if (length == 0) break;
|
||||||
|
|
||||||
final int type = ByteUtils.convertByteToInt(scanRecord[index]);
|
final int type = ByteUtils.getIntFromByte(scanRecord[index]);
|
||||||
|
|
||||||
//Done if our record isn't a valid type
|
//Done if our record isn't a valid type
|
||||||
if (type == 0) break;
|
if (type == 0) break;
|
||||||
|
|||||||
@@ -1,8 +1,18 @@
|
|||||||
package uk.co.alt236.btlescan.util;
|
package uk.co.alt236.btlescan.util;
|
||||||
|
|
||||||
public class ByteUtils {
|
import java.nio.ByteBuffer;
|
||||||
static final String HEXES = "0123456789ABCDEF";
|
|
||||||
|
|
||||||
|
public class ByteUtils {
|
||||||
|
private static final String HEXES = "0123456789ABCDEF";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a pretty representation of a Byte Array as a HEX String.
|
||||||
|
*
|
||||||
|
* Sample output: [01, 30, FF, AA]
|
||||||
|
*
|
||||||
|
* @param array the array
|
||||||
|
* @return the string
|
||||||
|
*/
|
||||||
public static String byteArrayToHexString(final byte[] array){
|
public static String byteArrayToHexString(final byte[] array){
|
||||||
final StringBuffer sb = new StringBuffer();
|
final StringBuffer sb = new StringBuffer();
|
||||||
boolean firstEntry = true;
|
boolean firstEntry = true;
|
||||||
@@ -21,8 +31,35 @@ public class ByteUtils {
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
public static int convertByteToInt(byte bite){
|
* Converts a byte to an int, preserving the sign.
|
||||||
|
*
|
||||||
|
* For example, FF will be converted to 255 and not -1.
|
||||||
|
*
|
||||||
|
* @param bite the bite
|
||||||
|
* @return the int from byte
|
||||||
|
*/
|
||||||
|
public static int getIntFromByte(final byte bite){
|
||||||
return Integer.valueOf(bite & 0xFF);
|
return Integer.valueOf(bite & 0xFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a byte array to an integer;
|
||||||
|
*
|
||||||
|
* @param bytes the bytes
|
||||||
|
* @return the int from byte array
|
||||||
|
*/
|
||||||
|
public static int getIntFromByteArray(final byte[] bytes) {
|
||||||
|
return ByteBuffer.wrap(bytes).getInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a byte array to a long;
|
||||||
|
*
|
||||||
|
* @param bytes the bytes
|
||||||
|
* @return the long from byte array
|
||||||
|
*/
|
||||||
|
public static long getLongFromByteArray(final byte[] bytes) {
|
||||||
|
return ByteBuffer.wrap(bytes).getLong();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user