本文将简单介绍如何搭建一套蓝牙定位系统,供移动客户端(包括android和iOS)定位。

1.准备设备

所需硬件设备:
(1)低功率蓝牙定位器若干(如:10个),网上有卖(单价从几十到几百都有)
(2)android设备一台,系统版本4.2以上(SDK版本大于17)
(3)iOS设备一台,支持蓝牙4.0 BLE

2.设置蓝牙定位器

移动设备扫描周边低功率蓝牙设备,可以获得蓝牙设备对应的Proximity UUID、Major、Minor等属性信息。而刚采购来的蓝牙设备属性可能都相同,互相区别不开,所以我们需要设置每台设备的属性。
设备厂商都会提供相关手机应用,共用户设置属性信息。给蓝牙设备装上电池,打开手机应用,靠近蓝牙设备就能发现,然后就可以设置其属性值了,其中:
UUID是一个32位的16进制数,表示设备厂商,该字段可以沿用出厂设置
Major表示不同区域(比如:某一楼层、某一地区),取值范围0到6万多
Minor表示不同的设备,取值范围0到6万多
样例:UUID = e2c56db5-dffb-48d2-b060-d0f5a71096e0, Major = 1001, Minor = 10001
每台设备设置完属性后准备一个标签,填上属性信息,贴到设备上,方便以后部署。

3.部署蓝牙设备

首先,准备目标场地地图数据,可以是基于经纬度坐标,也可以是简单图片坐标,看具体使用情况。
接下来,将蓝牙设备挨个部署到场地指定位置上,顺便记录每个设备地理坐标或图片坐标。
最后,得到一张表格信息,记录着每台蓝牙设备属性和位置信息。这张表就是整个定位系统的指纹库,为定位算法使用。
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
... ... ... ... ...
固定蓝牙设备到场地指定位置比较容易,不过记录设备坐标信息可能复杂一点,需要在地图或图片上获得相应位置点。可以开发一个App从而快速准确地记录位置信息,顺便将相关信息录入指纹库(数据库,比如:SQLite)。
部署蓝牙设备还有一个关注点就是部署间隔。低功率蓝牙设备容易受场地、环境影响,比较不稳定,所以根据场地条件每隔几米或十几米部署一台蓝牙设备。间隔太大会影响定位精度,不过太密也是资源浪费,不是越密集定位精度越高。

4.客户端App开发

客户端app主要功能就是扫描周围蓝牙设备,将设备列表信息上传定位服务器,从而获得定位效果,并展现给终端用户。

4.1 Android应用开发

工程所需SDK版本大于17。
1. App所需权限(AndroidManifest.xml文件)
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

2. 创建beacon数据项类

public class IBeaconRecord {public String address; // 设备地址(Mac)public String uuid;       // Proximity UUIDpublic int major;      // Majorpublic int minor;       // Minorpublic int rssi;        // 场强
}
其中,address属性可以不要,因为iOS设备获取不到该属性!

3. 创建扫描工具类

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<IBeaconRecord>private Map<String, List<IBeaconRecord>> mapBltScanResult;public BLEPositioning(Context ctx) {super();this.m_ctx = ctx;initParam();}/*** 初始化*/private void initParam() {handler = new Handler();mapBltScanResult = new HashMap<String, List<IBeaconRecord>>();// 设备SDK版本大于17(Build.VERSION_CODES.JELLY_BEAN_MR1)才支持BLE 4.0if (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() {@Overridepublic void run() {mBluetoothAdapter.stopLeScan(bltScanCallback);}}, 5 * 1000);mBluetoothAdapter.startLeScan(bltScanCallback); // 开始扫描}}/*** 请求定位服务,由你们完成,* 如果指纹数据在本地,定位算法就在当前App里完成*/public void requestServer(){// TODO // 利用mapBltScanResult(蓝牙扫描结果)请求定位服务或本地计算定位}/*** 蓝牙扫描回调,获取扫描获得的蓝牙设备信息*/private BluetoothAdapter.LeScanCallback bltScanCallback = new BluetoothAdapter.LeScanCallback() {@Overridepublic 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<IBeaconRecord> list = new ArrayList<IBeaconRecord>();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 iBeaconpatternFound = 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 iBeaconreturn 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);}
}

扫描结果放在mapBltScanResult里,该HashMap的key由设备Mac地址和名称组成(address_name),value是个ArrayList,记录着该蓝牙设备多次扫描得到的信息(IBeaconRecord)序列,请求定位服务或本地计算定位之前,这些序列要进行平均处理(其实只是平均rssi值)。经过RSSI值多次平均处理后,一定程度上减小蓝牙设备不稳定因素。

关于请求定位服务,展现定位效果,还有定位算法都不是本文重点!关于蓝牙定位算法也可以参考其他文献资料!

4.2 iOS应用开发

iOS部分参考了AirLocate源码(苹果官方蓝牙样例工程)。
1. 引用基础配置类“APLDefaults”(来自AirLocate)

APLDefaults.h文件

/*File: APLDefaults.hAbstract: Contains default values for the application.Version: 1.1Copyright (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.mAbstract: Contains default values for the application.Version: 1.1Copyright (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

2.  定义变量

    // 存储扫描获得的蓝牙设备信息// key - proximityUUID_Major_Minor// value - NSArray (CLBeacon)NSMutableDictionary *dicBeacons;CLLocationManager *locationManager;NSMutableDictionary *rangedRegions;     // 要扫描的regionNSTimer *timerPos;  // 定时器,用于控制扫描时间长短

3. 初始化

    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];}

4. 开始扫描、停止扫描和请求定位服务

// 开始扫描蓝牙
- (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来请求定位服务//
}
其中,请求定位服务部分每个人都不一样,依赖自身定位服务。
5. 监听回调,解析扫描获得的蓝牙设备信息,存入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 multipleregions.  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];}}}}
}

5. 定位服务开发

部署蓝牙设备时组建了最原始的蓝牙指纹库(数据表),利用这张表可以开发一套定位服务。
客户端上传过来的是一组蓝牙设备信息列表,例如:
{"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";});
}

根据客户端上传的设备列表信息和指纹库信息计算出一个位置点返回给客户端,这样一个定位服务算搞定了!目前有多种定位算法和技术,可以参考相关文献资料!

以上就是搭建蓝牙定位系统整个内容,谢谢!

如何搭建简易蓝牙定位系统相关推荐

  1. 服务器系统增加蓝牙功能,技术指导:怎么搭建简易蓝牙定位系统

    原标题:技术指导:怎么搭建简易蓝牙定位系统 位置服务的相关技术和产业正逐渐从室外定位行业转向室内定位行业发展,并提供了多种基于室内定位的位置服务.作为行业内知名的室内定位解决方案提供商,本篇SKYLA ...

  2. android 定位蓝牙,蓝牙如何定位,简易蓝牙定位系统的实现方法

    蓝牙如何定位,简易蓝牙定位系统的实现方法 1.准备设备 所需硬件设备: (1)低功率蓝牙定位器若干(如:10个),网上有卖(单价从几十到几百都有) (2)android设备一台,系统版本4.2以上(S ...

  3. 蓝牙定位系统有什么优势

    发电.供电.石油化工.钢铁冶金行业为生产设备密集型企业,生产现场错综复杂,稍有不慎便会发生危险:建筑工地现场施工作业中,存在着人员流动性大.现场状况杂乱.安全隐患难以察觉等问题:工厂安全管理混乱,事故 ...

  4. 搭建简易Linux局网服务器

    搭建简易Linux局网服务器 该文章转自 联信软件 作为桌面操作系统,Linux的人机界面可真是不敢恭维,但是,作为网络操作系统,其易用性(对于NOS而言)和高性能恐怕是很难有能出其右的.当然,这并不 ...

  5. Django搭建简易博客

    Django简易博客,主要实现了以下功能 连接数据库 创建超级用户与后台管理 利用django-admin-bootstrap美化界面 template,view与动态URL 多说评论功能 Markd ...

  6. CDH5.15离线搭建简易版集群(完整版)

    运用CDH5.15离线搭建简易版集群 关于CDH和Cloudera CDH(Cloudera的发行版,包括Apache Hadoop),是Hadoop众多分支中的一种,由Cloudera维护,基于稳定 ...

  7. 如何搭建简易又安全的企业内部文件服务器?

    提到搭建企业内部文件服务器,很多人的第一反应是Samba文件服务器. 确实,在过去的很多年里,大部分企业都是通过Windows Server的域控制器使企业内部员工方便地进行资源共享和使用网络打印机. ...

  8. Linux Centos7 搭建简易堡垒机安装jailkit实现chroot

    Linux Centos7 搭建简易堡垒机安装jailkit实现chroot 一.什么是堡垒机 堡垒机,即在一个特定的网络环境下,为了保障网络和数据不受来自外部和内部用户的入侵和破坏,而运用各种技术手 ...

  9. 用 kali 工具 apache 搭建简易网站 LAMP

    搭建简易网站 内网和外网的原理图示: 搭建步骤为LAMP L为linux,A为Apache,M代表Mysql,P代表php ~ 打开apache服务 service apache2 start # 打 ...

最新文章

  1. 机器学习四剑客4——PIL
  2. N-MOS 2N7002晶体管
  3. 销售订单行项目的装运点字段确认规则
  4. linux tomcat apr安装,Linux下Tomcat安装并开启APR模式-Go语言中文社区
  5. 科学家利用计算机模型,科学家尝试利用计算机模拟整个宇宙的演化
  6. python取前三位_python3 获取前几个高频列表元素
  7. php mysql 双条件排序_php – 是否有通过匹配多个条件进行排序的SQL技术?
  8. literal和meta的意义和用法
  9. secoclient在Mac下使用无法上网的解决办法
  10. 阿里图标库(iconfont)下载图标使用图标技巧(保姆级图文)
  11. 如何得到信号的幅度谱和相位谱
  12. 支付宝 手机h5支付
  13. 电源管理IC临近爆发,详解四大市场趋势
  14. 电脑或网页打开很慢是什么原因?
  15. 利用输入法输入汉字,如何统计字数
  16. MySQL (四)------DML操作表记录-增删改【重点】DQL操作表记录-查询【重点】
  17. 【转】objective-c基本数据类型之输出格式符
  18. 《GAMES104-现代游戏引擎:从入门到实践》-02 学习笔记
  19. 【转】达摩祖师《悟性论》
  20. 雄关漫道真如铁,而今迈步从头越【我的2017】

热门文章

  1. 计算机网络知识梳理总结(For 考研复试面试)
  2. 东方通文件中文乱码问题
  3. python3笔记_Python3学习笔记(四)
  4. 数据结构实验课:实验六、图的遍历操作及应用
  5. 数据结构与算法A实验六图论---7-1 列出连通集(BFS DFS)
  6. word如何设置自动保存?
  7. 非线性光纤光学中分步傅里叶算法(SSFFT)的matlab代码实现
  8. RocketMQ学习笔记(持续更新)
  9. 免费打印控件Lodop
  10. 1 springboot整合elasticsearch入门例子