本文将简单介绍如何搭建一套蓝牙定位系统,供移动客户端(包括android和iOS)定位。
UUID | Major | Minor | Lat | Lon |
e2c56db5-dffb-48d2-b060-d0f5a71096e0 | 1001 | 10001 | 39.45678 | 116.23456 |
e2c56db5-dffb-48d2-b060-d0f5a71096e0 | 1001 | 10002 | 39.45674 | 116.23476 |
... | ... | ... | ... | ... |
public class IBeaconRecord {
public String address; // 设备地址(Mac)
public String uuid; // Proximity UUID
public int major; // Major
public int minor; // Minor
public int rssi; // 场强
}
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.example.vo.IBeaconRecord;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.os.Build;
import android.os.Handler;
public class BLEPositioning {
private Context m_ctx;
private Handler handler;
private BluetoothManager bluetoothManager;
private BluetoothAdapter mBluetoothAdapter;
// 存储蓝牙扫描结果,key - name_address, value - List
private Map> mapBltScanResult;
public BLEPositioning(Context ctx) {
super();
this.m_ctx = ctx;
initParam();
}
/**
* 初始化
*/
private void initParam() {
handler = new Handler();
mapBltScanResult = new HashMap>();
// 设备SDK版本大于17(Build.VERSION_CODES.JELLY_BEAN_MR1)才支持BLE 4.0
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
bluetoothManager = (BluetoothManager) this.m_ctx
.getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
}
}
/**
* 开始扫描蓝牙设备
*/
public void startScan()
{
mapBltScanResult.clear();
if (mBluetoothAdapter != null && mBluetoothAdapter.isEnabled()) {
// 5秒后停止扫描,毕竟扫描蓝牙设备比较费电,根据定位及时性自行调整该值
handler.postDelayed(new Runnable() {
@Override
public void run() {
mBluetoothAdapter.stopLeScan(bltScanCallback);
}
}, 5 * 1000);
mBluetoothAdapter.startLeScan(bltScanCallback); // 开始扫描
}
}
/**
* 请求定位服务,由你们完成,
* 如果指纹数据在本地,定位算法就在当前App里完成
*/
public void requestServer()
{
// TODO
// 利用mapBltScanResult(蓝牙扫描结果)请求定位服务或本地计算定位
}
/**
* 蓝牙扫描回调,获取扫描获得的蓝牙设备信息
*/
private BluetoothAdapter.LeScanCallback bltScanCallback = new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice device, int rssi,
byte[] scanRecord) {
/**
* 参数列表描述
* 1.device - BluetoothDevice类对象,
* 通过该对象可以得到硬件地址(比如"00:11:22:AA:BB:CC")、设备名称等信息
* 2.rssi - 蓝牙设备场强值,小于0的int值
* 3.scanRecord - 这里内容比较丰富,像UUID、Major、Minor都在这里
*/
IBeaconRecord record = new IBeaconRecord();
if (fromScanData(scanRecord, record)) {
String address = device.getAddress(); // 获取Mac地址
String name = device.getName(); // 获取设备名称
String key = name + "_" + address;
record.address = address; // Mac地址
record.rssi = rssi; // 场强
if (mapBltScanResult.containsKey(key)) {
mapBltScanResult.get(key).add(record);
} else {
ArrayList list = new ArrayList();
list.add(record);
mapBltScanResult.put(key, list);
}
}
}
};
/**
* 解析蓝牙信息数据流
* 注:该段代码是从网上看到的,来源不详
* @param scanData
* @param record
* @return
*/
private boolean fromScanData(byte[] scanData, IBeaconRecord record) {
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) {
return false;
} 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) {
return false;
}
startByte++;
}
if (patternFound == false) {
// This is not an iBeacon
return false;
}
// 获得Major属性
record.major = (scanData[startByte + 20] & 0xff) * 0x100
+ (scanData[startByte + 21] & 0xff);
// 获得Minor属性
record.minor = (scanData[startByte + 22] & 0xff) * 0x100
+ (scanData[startByte + 23] & 0xff);
// record.tx_power = (int) scanData[startByte + 24]; // this one is
// signed
// record.accuracy = calculateAccuracy(record.tx_power, record.rssi);
// if (record.accuracy < 0) {
// return false;
// }
try {
byte[] proximityUuidBytes = new byte[16];
System.arraycopy(scanData, startByte + 4, proximityUuidBytes, 0, 16);
String hexString = bytesToHex(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));
// beacon.put("proximity_uuid", sb.toString());
// 获得UUID属性
record.uuid = sb.toString();
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
private char[] hexArray = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', 'a', 'b', 'c', 'd', 'e', 'f' };
private String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
int v;
for (int j = 0; j < bytes.length; j++) {
v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
}
APLDefaults.h文件
/*
File: APLDefaults.h
Abstract: Contains default values for the application.
Version: 1.1
Copyright (C) 2014 Apple Inc. All Rights Reserved.
*/
extern NSString *BeaconIdentifier;
@interface APLDefaults : NSObject
+ (APLDefaults *)sharedDefaults;
@property (nonatomic, copy, readonly) NSArray *supportedProximityUUIDs;
@property (nonatomic, copy, readonly) NSUUID *defaultProximityUUID;
@property (nonatomic, copy, readonly) NSNumber *defaultPower;
@end
APLDefaults.m文件
/*
File: APLDefaults.m
Abstract: Contains default values for the application.
Version: 1.1
Copyright (C) 2014 Apple Inc. All Rights Reserved.
*/
#import "APLDefaults.h"
NSString *BeaconIdentifier = @"com.example.apple-samplecode.AirLocate";
@implementation APLDefaults
- (id)init
{
self = [super init];
if(self)
{
// uuidgen should be used to generate UUIDs.
_supportedProximityUUIDs = @[[[NSUUID alloc] initWithUUIDString:@"E2C56DB5-DFFB-48D2-B060-D0F5A71096E0"],
[[NSUUID alloc] initWithUUIDString:@"5A4BCFCE-174E-4BAC-A814-092E77F6B7E5"],
[[NSUUID alloc] initWithUUIDString:@"74278BDA-B644-4520-8F0C-720EAF059935"]];
_defaultPower = @-59;
}
return self;
}
+ (APLDefaults *)sharedDefaults
{
static id sharedDefaults = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedDefaults = [[self alloc] init];
});
return sharedDefaults;
}
- (NSUUID *)defaultProximityUUID
{
return _supportedProximityUUIDs[0];
}
@end
// 存储扫描获得的蓝牙设备信息
// key - proximityUUID_Major_Minor
// value - NSArray (CLBeacon)
NSMutableDictionary *dicBeacons;
CLLocationManager *locationManager;
NSMutableDictionary *rangedRegions; // 要扫描的region
NSTimer *timerPos; // 定时器,用于控制扫描时间长短
dicBeacons = [[NSMutableDictionary alloc] init];
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self; // 当前类接收回调,从而获得蓝牙设备信息
// Populate the regions we will range once.
rangedRegions = [[NSMutableDictionary alloc] init];
for (NSUUID *uuid in [APLDefaults sharedDefaults].supportedProximityUUIDs)
{
CLBeaconRegion *region = [[CLBeaconRegion alloc] initWithProximityUUID:uuid identifier:[uuid UUIDString]];
rangedRegions[region] = [NSArray array];
}
// 开始扫描蓝牙
- (void)startScanning
{
// 定时3.0秒后请求定位服务,时间间隔自行设置,只要有足够的扫描时间即可
timerPos = [NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(startPositioning) userInfo:nil repeats:NO];
[dicBeacons removeAllObjects];
// 开始扫描
for (CLBeaconRegion *region in rangedRegions)
{
[locationManager startRangingBeaconsInRegion:region];
}
}
// 停止扫描蓝牙
- (void)stopScanning
{
// 停止扫描
for (CLBeaconRegion *region in rangedRegions)
{
[locationManager stopRangingBeaconsInRegion:region];
}
}
// 请求定位服务
- (void)startPositioning
{
[self stopScanning]; // 停止扫描
// 以下根据扫描结果dicBeacons来请求定位服务
//
}
#pragma mark - Location manager delegate
- (void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region
{
/*
CoreLocation will call this delegate method at 1 Hz with updated range information.
Beacons will be categorized and displayed by proximity. A beacon can belong to multiple
regions. It will be displayed multiple times if that is the case. If that is not desired,
use a set instead of an array.
*/
for (NSNumber *range in @[@(CLProximityUnknown), @(CLProximityImmediate), @(CLProximityNear), @(CLProximityFar)])
{
NSArray *proximityBeacons = [beacons filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"proximity = %d", [range intValue]]];
for (int i = 0; i < [proximityBeacons count]; i++) {
CLBeacon *beacon = [proximityBeacons objectAtIndex:i];
// 场强过滤,RSSI值要在-90到0之间
if (beacon.rssi < 0 && beacon.rssi > -90) {
NSString *strKey = [NSString stringWithFormat:@"%@_%@_%@",[beacon.proximityUUID UUIDString], beacon.major, beacon.minor];
if ([dicBeacons objectForKey:strKey]) {
[[dicBeacons objectForKey:strKey] addObject:beacon];
} else {
NSMutableArray *arrBeacons = [[NSMutableArray alloc] init];
[arrBeacons addObject:beacon];
[dicBeacons setObject:arrBeacons forKey:strKey];
}
}
}
}
}
{
"ble_arr” = (
{
major = 1001;
minor = 10006;
rssi = "-65";
uuid = " E2C56DB5-DFFB-48D2-B060-D0F5A71096E0";
},
{
major = 1001;
minor = 10002;
rssi = "-72";
uuid = " E2C56DB5-DFFB-48D2-B060-D0F5A71096E0";
},
{
major = 1001;
minor = 10005;
rssi = "-49";
uuid = " E2C56DB5-DFFB-48D2-B060-D0F5A71096E0";
},
{
major = 1001;
minor = 10008;
rssi = "-74";
uuid = " E2C56DB5-DFFB-48D2-B060-D0F5A71096E0";
},
{
major = 1001;
minor = 10001;
rssi = "-65";
uuid = " E2C56DB5-DFFB-48D2-B060-D0F5A71096E0";
},
{
major = 1001;
minor = 10004;
rssi = "-76";
uuid = " E2C56DB5-DFFB-48D2-B060-D0F5A71096E0";
},
{
major = 1001;
minor = 10007;
rssi = "-66";
uuid = " E2C56DB5-DFFB-48D2-B060-D0F5A71096E0";
},
{
major = 1001;
minor = 17010;
rssi = "-67";
uuid = " E2C56DB5-DFFB-48D2-B060-D0F5A71096E0";
}
);
}