iBeacon是什么?
维基百科:iBeacon是苹果公司提出的”一种可以让附近手持电子设备检测到的一种新的低功耗、低成本信号传送器”的一套可用于室内定位系统的协议。这种技术可以使一个智能手机或其他装置在一个iBeacon基站的感应范围内执行相应的命令。
百度百科:iBeacon是苹果公司2013年9月发布的移动设备OS(iOS7)上配备的新功能。其工作方式是,配备有低功耗蓝牙(BLE)通信功能的设备使用BLE技术向周围发送自己特有的ID,接收到该ID的应用软件会根据该ID采取一些行动。
使用场景:
- 推送感知场景的信息
当用户移动到他们感兴趣的区域时,iBeacons 可以用来给他们推送信息。博物馆就是一个典型的例子,设想在每一个展品的位置放置一个 iBeacons 设备,当用户走近展品的时候,手机应用自动展示看到展品的更多信息会有多棒! 这就需要手机应用侦听发射器来了解有哪些发射器接近哪些展品,通过这样的匹配,发射器将定位用户在博物馆中的位置并让应用做出合理的响应。
- 定位追踪
作为推送感知场景的信息概念的扩展,你也可以使用 iBeacons 作为一种追踪用户的方式。设想在一个博物馆或者杂货店的建筑中遍布 iBeacons ,随着用户的移动,由他们通过发射器的顺序,你可以侦测出他们的移动轨迹。这允许你追踪用户的行踪,并且总结出最普遍的行进路线和行进模式。
从开发者角度的思考:
iBeacon向四面八方不停地广播信号,就像是往平静的水面上扔了一块石子,泛起层层涟漪(俗称水波),波峰相当于iBeacon的RSSI(接受信号强度指示),越靠近中心点的地方波峰越高(RSSI越大),这个波峰的大小(RSSI的值)受到扔石子时用力大小(发射功率)和水质(周围环境因子)的影响,离中心点越远水波越趋向于平静,超过了一定值,水波会消失于无形,也就是说iBeacon向外广播的距离是有范围的,超过了这个范围,将接受不到iBeacon的信号。
总体来看,iBeacon中有两个角色:发射者 :各个硬件设备;接受者:智能终端(手机),发射者通过BLE 的广告通信通道,以一定时间间隔向外广播数据包(一般是每秒两三次),接收者可以通过终端提供的功能来接收,达到信息的交互.
每个信号中至少携带了三个主要信息:UUID, Major, Minor,这三个信号组成了一个 iBeacon 的唯一标识符.
如何接收iBeacon?
作为iOS开发者,这里有一个先天优势,每一台iPhone设备都可以作为“发射者,所以我们需要准备两台iPhone手机,其中一台手机去AppStore下载AirBeacon应用,用于发射iBeacon广播信号(发射者),另外一台用于接收调试
实际场景肯定不是用iPhone设备作为发射,一般都有很多第三方的硬件厂商做这个,比较主流的生产商包括 Estimote 、 Aruba 和 Radius Networks .如果你正在打算使用 iBeacons ,上边的任何一家都会是个不错的选择。
一个基站主要有三部分标识:
- 1. UUID,形如:206A2476-D4DB-42F0-BF73-030236F2C756。用来标识某一个公司。比如,某个房地产公司的全部的基站都用同一个UUID。
- 2. major,用来标识某一类的beacon。比如这个房地产公司的北京的房子都设定为1,上海的都设定为2。
- 3. minor,用来标识某一个特定的beacon。比如某栋楼的某个基站。
用这个房地产商开发的app就可以获取到UUID,和后面的major值和minor值。当app进入beacon的区域,探测到UUID:”206A2476-D4DB-42F0-BF73-030236F2C756“,major为1,minor为20。那么就表明这个用户在这个房地产商的北京楼盘的编号20的楼盘。这栋楼的说明文字、图片或者视频等就可以展现在用户面前。
iOS中相关API和使用方法,大致代码
- 需要打开GPS定位和蓝牙,在Info.plist 增加 Privacy – Location Always Usage Description
- iBeacon 的 API 是在 CoreLocation, 但iBeacon 必须要打开蓝牙,如果需要判断蓝牙,需要用到 CoreBluetooth 框架.
- Monitoring和 Ranging 是两种监测方式,可以一起用,但是需要区分业务需求,两种一起用会有小坑.
- CLLocationManager
locationManager = CLLocationManager.init() // 遵循代理 locationManager?.delegate = self // 请求用户授权定位 locationManager?.requestAlwaysAuthorization()
2.CLBeaconRegion 的创建
//唯一标示,其实有三个,包括major,minor let ZWUUID:UUID = UUID(uuidString: "7E66DA30-0A96-4DB5-A15B-066CE9032E70")! beaconRegin = CLBeaconRegion(proximityUUID: ZWUUID, major: 6, minor: 3, identifier: "ZWIBeacon") //通知退出和进入 beaconRegin?.notifyOnExit = true beaconRegin?.notifyOnEntry = true //补充说明: //仅使用proximityUUID来初始化区域,major,minor值将作为通配符。只要是区域内的iBeacon的proximityUUID与此proximityUUID相同,不管major, minor是什么值,都能被检测到。 CLBeaconRegion(proximityUUID:identifier:) //使用proximityUUID和major来初始化区域,minor值将作为通配符。区域内的iBeacon的proximityUUID和major与此proximityUUID和major相同时,不论minor为何值,都能被检测到。 CLBeaconRegion(proximityUUID:major:identifier:) //使用proximityUUID, major, minor来初始化,只能检测到区域内相同proximityUUID, major, minor的iBeacon设备。 CLBeaconRegion(proximityUUID:major: minor:identifier:)
3.可用两种方式检测区域 Monitoring或Ranging方式。
Monitoring方式: 可以用来在设备进入/退出某个地理区域时获得通知, 使用这种方法可以在应用程序的后台运行时检测iBeacon,但是只能同时检测20个region区域,并且不能够推测设备与iBeacon的距离。
locationManager?.startMonitoring(for: beaconRegin!) locationManager?.stopMonitoring(for: beaconRegin!) // 设备进入该区域时的回调 func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) { } // 设备退出该区域时的回调 func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) { } // 有错误产生时的回调 func locationManager(_ manager: CLLocationManager, monitoringDidFailFor region: CLRegion?, withError error: Error) { }
Ranging方式: 可以用来检测某区域内的所有iBeacons。
locationManager?.startRangingBeacons(in: beaconRegin!) locationManager?.stopRangingBeacons(in: beaconRegin!) // 检测到区域内的iBeacons时回调此函数,差不多1s刷新一次,这个方法会返回一个 CLBeacon 的数组,根据 CLBeacon 的 proximity 属性就可以判断设备和 beacon 之间的距离, // proximity 属性有四个可能的值,unknown、immediate、near 和 far, 另外 CLBeacon 还有 accuracy 和 rssi 两个属性能提供更详细的距离数据 func locationManager(_ manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], in region: CLBeaconRegion) { } // 有错误时候的回调 func locationManager(_ manager: CLLocationManager, rangingBeaconsDidFailFor region: CLBeaconRegion, withError error: Error) { }
注意:如果搜索不到设备广播,看看是不是因为UUID等唯一标示是不是不一样
更多想法讨论
1.距离是否准确
网上有许多围绕每次 ranging event(指的就是范围模式的 didRangeBeacons 回调) 所返回的 CLLocationAccuracy 值的讨论。人们常把这个值当作手机与发射器的实际距离值。以我的经验来看,你当然可以把这个值当作实际的距离,但它并不总是那么准确。苹果文档建议你首先利用 CLProxity 枚举值来初步判定设备距离的远近,然后再用 CLLocationAccuracy 的值来区分其中接近度相近的值。
2.监听超过 20 个设备
正像我前边介绍的那样,你现在只能监听 20 个 iBeacons ,如果你需要监听超过 20 个设备,你将需要在应用运行的过程中更改监听的设置,一种实现方案是用图表来展示你的 iBeacons 网络,在网络中定义最顶层的 iBeacons 以及如果这些彼此接近的情况下能够连接到的边界。这样你就可以快速的查找到最接近的 20 个 iBeacons 并监听他们。这需要很多的工作,但是定义一个这样的拓扑是一种实现 20 个 iBeacons 限制的方式。
一个完整的Beacon例子:
import UIKit import CoreLocation import CoreBluetooth //蓝牙开启通知 // did centralManager enable notification let BluetoothNotificationManagerEnable = "BabyNotificationAtCentralManagerEnable" // 蓝牙弹框是需要弹出 let BluetoothAlertIsShow = "BluetoothAlertIsShow" // 搜索到设备数组 typealias IbeaconSearchResults = (([CLBeacon]) -> ()) class IbeaconManager: NSObject { static let `default` = IbeaconManager() var searchResultsCallback: IbeaconSearchResults? fileprivate var beaconSendRegion: CLBeaconRegion! // 发送 fileprivate var beaconReceiveRegion: CLBeaconRegion! // 接受 fileprivate var locationManager: CLLocationManager! fileprivate var beaconPeripheralData: NSDictionary! fileprivate var peripheraManager: CBPeripheralManager! var location: Float = 0.0 //距离 let beaconIdentifier = "ibeaconTest" let defaultUUIDString = "XXXX-XXXXXX-XXXXXX-XXXXXXXXXX" override init() { super.init() // 发射信号 // 此处代码用另一部手机运行 模拟ibeacon设备发送信号 beaconSendRegion = CLBeaconRegion(proximityUUID: UUID(uuidString: defaultUUIDString)!, major: 1234, minor: 5678, identifier: beaconIdentifier) beaconPeripheralData = beaconSendRegion.peripheralData(withMeasuredPower: nil) peripheraManager = CBPeripheralManager(delegate: self, queue: nil) // 接受信号 locationManager = CLLocationManager() locationManager.delegate = self locationManager.requestAlwaysAuthorization() beaconReceiveRegion = CLBeaconRegion(proximityUUID: UUID(uuidString: defaultUUIDString)!, identifier: beaconIdentifier) beaconReceiveRegion.notifyEntryStateOnDisplay = true //请求一直允许定位 locationManager.requestAlwaysAuthorization() beaconReceiveRegion.notifyEntryStateOnDisplay = true } /// 开始扫描 func startRunningBeacons() { //开始扫描 locationManager.startMonitoring(for: beaconReceiveRegion) locationManager.startRangingBeacons(in: beaconReceiveRegion) } } extension IbeaconManager: CLLocationManagerDelegate { //进入beacon区域 func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) { locationManager.startRangingBeacons(in: beaconReceiveRegion) print( "进入beacon区域") } //离开beacon区域 func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) { locationManager.stopRangingBeacons(in: beaconReceiveRegion) print("离开beacon区域") } func locationManager(_ manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], in region: CLBeaconRegion) { //返回是扫描到的beacon设备数组,这里取第一个设备 guard beacons.count > 0 else { return } self.searchResultsCallback?(beacons) // 下面为调试信息 let beacon = beacons.first! print("major====",beacon.major) print("minor====",beacon.minor) //accuracy可以获取到当前距离beacon设备距离 let location = String(format: "%.3f", beacon.accuracy) print( "距离第一个beacon\(location)m") } func locationManager(_ manager: CLLocationManager, monitoringDidFailFor region: CLRegion?, withError error: Error) { print("Failed monitoring region: \(error.localizedDescription)") } func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { print("Location manager failed: \(error.localizedDescription)") } } extension IbeaconManager: CBPeripheralManagerDelegate { func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) { switch peripheral.state { case .poweredOn: peripheraManager.startAdvertising(beaconPeripheralData as? [String : Any]) print("蓝牙打开了!=============================") print(beaconReceiveRegion.proximityUUID) print(beaconReceiveRegion.major) print(beaconReceiveRegion.minor) print(beaconReceiveRegion.identifier) print("蓝牙打开了!=============================") UserDefaults.standard.set(true, forKey: BluetoothNotificationManagerEnable) UserDefaults.standard.synchronize() case .poweredOff: print("蓝牙未打开") UserDefaults.standard.set(false, forKey: BluetoothNotificationManagerEnable) UserDefaults.standard.synchronize() default: peripheraManager.stopAdvertising() } } }
需要使用的地方
/// 开始搜索蓝牙列表 | 只要搜索到了就记录值 用于对比教师课程里的UUID是否跟这个一致 | 如果一致说明在蓝牙搜索的范围内 可以执行签到 如果没有则不在搜索范围内 func startBlueToothSearch(blueToothNotOpen: (()->())?) { IbeaconManager.default.startRunningBeacons() IbeaconManager.default.searchResultsCallback = { (ibeacons) in var location = Double(1000) for ibeacon in ibeacons { let majorMinor = "\(ibeacon.major)\(ibeacon.minor)" // 值越小代表距离最近 ibeacon.accuracy 为距离 if ibeacon.accuracy <= location { location = ibeacon.accuracy } print("major====",ibeacon.major) print("minor====",ibeacon.minor) let location = String(format: "%.3f", ibeacon.accuracy) print( "距离beacon====\(location)m") } } // 检测蓝牙是否打开 if let enable = UserDefaults.standard.value(forKey: BluetoothNotificationManagerEnable) as? Bool { if enable == false { blueToothNotOpen?() } }else { blueToothNotOpen?() } }