软硬件要求:Android4.3及以上中支持BLE技术,同时蓝牙需要满足Bluetooth4.0及以上。
iBeacon的工作原理是基于Bluetooth Low Energy(BLE)低功耗蓝牙传输技术,iBeacon基站不断向四周发送蓝牙信号,当智能设备进入设定区域时,就能够收到信号。只要满足iBeacon技术标准的都可以使用,所以Android也能够支持iBeacon。Google在Android4.3中支持BLE技术,同时蓝牙需要满足Bluetooth4.0及以上。
定位一直是非常关键的功能。通过iBeacon基站的部署能够实现室内导航,同时通过蓝牙推送信息,iBeacon在商场零售或者一些公共服务领域如体育馆、博物馆能提供非常棒的体验。尤其是蓝牙不错传输距离、低功耗、以及信号加密使得iBeacon在移动支付领域也非常有前景。总之,iBeacon的潜力似乎是无穷大,也受到了越来越多的关注。
要了解iBeacon是如何工作首先我们要了解BLE。BLE(也称为Bluetooth Smart)最早追溯到Nokia于2006年提出的Wibree,后来融合进了蓝牙标准,成为Bluetooth4.0的一部分。目前我们经常能看到3种蓝牙设备:
BLE与传统的蓝牙相比最大的优势是功耗降低90%,同时传输距离增大(超过100米)、安全和稳定性提高(支持AES加密和CRC验证)。iBeacon同时有一些自己的特点:
iBeacon是如何工作呢?实际上iBeacon基站通过蓝牙的广播频道不断向外发送位置信息,发送频率越快越耗电。也就是说iBeacon并不推送消息,而只是用于定位,推送消息的功能必须由App来完成。苹果定义了iBeacon 其中32位广播的数据格式。
UUID+Major+Minor就构成了一个Beacon的识别号,有点类似于网络中的IP地址。TX Power用于测距,iBeacon目前只定义了大概的3个粗略级别:
下面分步骤来实现检测IBeacon热点。
一、在你的主清单中AndroidManifest.xml中添加权限:
//此权限必须要添加,否则不能正常使用此功能
二、检测手机是否支持蓝牙,并获取mBluetoothAdapter 对象
if (!getPackageManager().hasSystemFeature(
PackageManager.FEATURE_BLUETOOTH_LE))
{
Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT)
.show();
finish();
}
final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
if (mBluetoothAdapter == null)
{
Toast.makeText(this, R.string.error_bluetooth_not_supported,
Toast.LENGTH_SHORT).show();
finish();
return;
}
三、实现LeScanCallback回调接口
设备每次检测到一个蓝牙设备,就会回调这个接口中的onLeScan()方法,并且传入扫描到的device,rssi,scanRecord等参数。
private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback()
{
@Override
public void onLeScan(final BluetoothDevice device, int rssi,
byte[] scanRecord)
{
//在这里处理扫描到的参数
//判断是不是IBeacon设备,做相应的处理。
}
};
四、处理扫描到的参数的方法
public class iBeaconClass
{
static public class iBeacon
{
public String name;
public int major;
public int minor;
public String proximityUuid;
public String bluetoothAddress;
public int txPower;
public int rssi;
}
/**
* 将扫描到的信息传入这个方法
* 该方法会判断扫描到的设备是不是IBeacon
* 如果是就返回一个IBeacon对象
* 如果不是就返回null
* @param device
* @param rssi
* @param scanData
* @return
*/
public static iBeacon fromScanData(BluetoothDevice device, int rssi,
byte[] scanData)
{
int startByte = 2;
boolean patternFound = false;
while (startByte <= 5)
{
if (((int) scanData[startByte + 2] & 0xff) == 0x02
&& ((int) scanData[startByte + 3] & 0xff) == 0x15)
{
// yes! This is an iBeacon
patternFound = true;
break;
} else if (((int) scanData[startByte] & 0xff) == 0x2d
&& ((int) scanData[startByte + 1] & 0xff) == 0x24
&& ((int) scanData[startByte + 2] & 0xff) == 0xbf
&& ((int) scanData[startByte + 3] & 0xff) == 0x16)
{
iBeacon iBeacon = new iBeacon();
iBeacon.major = 0;
iBeacon.minor = 0;
iBeacon.proximityUuid = "00000000-0000-0000-0000-000000000000";
iBeacon.txPower = -55;
return iBeacon;
} else if (((int) scanData[startByte] & 0xff) == 0xad
&& ((int) scanData[startByte + 1] & 0xff) == 0x77
&& ((int) scanData[startByte + 2] & 0xff) == 0x00
&& ((int) scanData[startByte + 3] & 0xff) == 0xc6)
{
iBeacon iBeacon = new iBeacon();
iBeacon.major = 0;
iBeacon.minor = 0;
iBeacon.proximityUuid = "00000000-0000-0000-0000-000000000000";
iBeacon.txPower = -55;
return iBeacon;
}
startByte++;
}
if (patternFound == false)
{
// This is not an iBeacon
return null;
}
iBeacon iBeacon = new iBeacon();
iBeacon.major = (scanData[startByte + 20] & 0xff) * 0x100
+ (scanData[startByte + 21] & 0xff);
iBeacon.minor = (scanData[startByte + 22] & 0xff) * 0x100
+ (scanData[startByte + 23] & 0xff);
iBeacon.txPower = (int) scanData[startByte + 24]; // this one is signed
iBeacon.rssi = rssi;
// AirLocate:
// 02 01 1a 1a ff 4c 00 02 15 # Apple's fixed iBeacon advertising prefix
// e2 c5 6d b5 df fb 48 d2 b0 60 d0 f5 a7 10 96 e0 # iBeacon profile
// uuid
// 00 00 # major
// 00 00 # minor
// c5 # The 2's complement of the calibrated Tx Power
// Estimote:
// 02 01 1a 11 07 2d 24 bf 16
// 394b31ba3f486415ab376e5c0f09457374696d6f7465426561636f6e00000000000000000000000000000000000000000000000000
byte[] proximityUuidBytes = new byte[16];
System.arraycopy(scanData, startByte + 4, proximityUuidBytes, 0, 16);
String hexString = bytesToHexString(proximityUuidBytes);
StringBuilder sb = new StringBuilder();
sb.append(hexString.substring(0, 8));
sb.append("-");
sb.append(hexString.substring(8, 12));
sb.append("-");
sb.append(hexString.substring(12, 16));
sb.append("-");
sb.append(hexString.substring(16, 20));
sb.append("-");
sb.append(hexString.substring(20, 32));
iBeacon.proximityUuid = sb.toString();
if (device != null)
{
iBeacon.bluetoothAddress = device.getAddress();
iBeacon.name = device.getName();
}
return iBeacon;
}
private static String bytesToHexString(byte[] src)
{
StringBuilder stringBuilder = new StringBuilder("");
if (src == null || src.length <= 0)
{
return null;
}
for (int i = 0; i < src.length; i++)
{
int v = src[i] & 0xFF;
String hv = Integer.toHexString(v);
if (hv.length() < 2)
{
stringBuilder.append(0);
}
stringBuilder.append(hv);
}
return stringBuilder.toString();
}
}
五、开启蓝牙
mBluetoothAdapter.enable();
六、开始扫描
mBluetoothAdapter.startLeScan(mLeScanCallback);
七、根据场强计算距离
/**
* 根据场强计算距离
*
* 计算公式:
* d = 10^((abs(RSSI) - A) / (10 * n))
* 其中:
* d - 计算所得距离
* RSSI - 接收信号强度(负值)
* A - 发射端和接收端相隔1米时的信号强度
* n - 环境衰减因子
*
* 计算公式的代码实现:
* - (float)calcDistByRSSI:(int)rssi
* {
* int iRssi = abs(rssi);
* float power = (iRssi-59)/(10*2.0);
* return pow(10, power);
* }
*
* 传入RSSI值,返回距离(单位:米)。其中,A参数赋了59,n赋了2.0。
*
* @param rssi 场强
* @return double
*/
public static double calcDistByRSSI(int rssi) {
double iRssi = abs(rssi);
double power = ((iRssi - 59) / (10 * 2.0));
return Math.pow(10, power);
}
public
class
MainActivity
extends
Activity {
private
BluetoothAdapter mBluetoothAdapter;
@Override
protected
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
if
(mBluetoothAdapter ==
null
|| !mBluetoothAdapter.isEnabled()) {
Intent enableBluetooth =
new
Intent(
BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBluetooth,
1
);
}
mBluetoothAdapter.startLeScan(mLeScanCallback);
}
private
BluetoothAdapter.LeScanCallback mLeScanCallback =
new
BluetoothAdapter.LeScanCallback() {
@Override
public
void
onLeScan(
final
BluetoothDevice device,
final
int
rssi,
final
byte
[] scanRecord) {
int
startByte =
2
;
boolean
patternFound =
false
;
// 寻找ibeacon
while
(startByte <=
5
) {
if
(((
int
) scanRecord[startByte +
2
] &
0xff
) ==
0x02
&&
// Identifies
// an
// iBeacon
((
int
) scanRecord[startByte +
3
] &
0xff
) ==
0x15
) {
// Identifies
// correct
// data
// length
patternFound =
true
;
break
;
}
startByte++;
}
// 如果找到了的话
if
(patternFound) {
// 转换为16进制
byte
[] uuidBytes =
new
byte
[
16
];
System.arraycopy(scanRecord, startByte +
4
, uuidBytes,
0
,
16
);
String hexString = bytesToHex(uuidBytes);
// ibeacon的UUID值
String uuid = hexString.substring(
0
,
8
) +
"-"
+ hexString.substring(
8
,
12
) +
"-"
+ hexString.substring(
12
,
16
) +
"-"
+ hexString.substring(
16
,
20
) +
"-"
+ hexString.substring(
20
,
32
);
// ibeacon的Major值
int
major = (scanRecord[startByte +
20
] &
0xff
) *
0x100
+ (scanRecord[startByte +
21
] &
0xff
);
// ibeacon的Minor值
int
minor = (scanRecord[startByte +
22
] &
0xff
) *
0x100
+ (scanRecord[startByte +
23
] &
0xff
);
String ibeaconName = device.getName();
String mac = device.getAddress();
int
txPower = (scanRecord[startByte +
24
]);
Log.d(
"BLE"
,bytesToHex(scanRecord));
Log.d(
"BLE"
,
"Name:"
+ ibeaconName +
"\nMac:"
+ mac
+
" \nUUID:"
+ uuid +
"\nMajor:"
+ major +
"\nMinor:"
+ minor +
"\nTxPower:"
+ txPower +
"\nrssi:"
+ rssi);
Log.d(
"BLE"
,
"distance:"
+calculateAccuracy(txPower,rssi));
}
}
};
static
final
char
[] hexArray =
"0123456789ABCDEF"
.toCharArray();
private
static
String bytesToHex(
byte
[] bytes) {
char
[] hexChars =
new
char
[bytes.length *
2
];
for
(
int
j =
0
; j < bytes.length; j++) {
int
v = bytes[j] &
0xFF
;
hexChars[j *
2
] = hexArray[v >>>
4
];
hexChars[j *
2
+
1
] = hexArray[v &
0x0F
];
}
return
new
String(hexChars);
}
protected
static
double
calculateAccuracy(
int
txPower,
double
rssi) {
if
(rssi ==
0
) {
return
-
1.0
;
// if we cannot determine accuracy, return -1.
}
double
ratio = rssi *
1.0
/ txPower;
if
(ratio <
1.0
) {
return
Math.pow(ratio,
10
);
}
else
{
double
accuracy = (
0.89976
) * Math.pow(ratio,
7.7095
) +
0.111
;
return
accuracy;
}
}
}