现在公司手头的项目基本上是基于蓝牙的开发,所以对低功耗蓝牙还是有一定的了解,今天特意把我写的蓝牙模块儿整理出来,供大家参考。
一 : 初识低功耗蓝牙
1, 中心(central)和外设(peripheral)
刚接触蓝牙开发的时候,找了许多专业的资料,讲到蓝牙开发分为两种模式,中心模式(central),和外设模式(peripheral)。云里雾里,以至于搞了两三天都不知道自己做的东西到底是做为中心还是做为外设。其实远远没有那么复杂,一般来讲,我们做的需要在软件内连接硬件,通过连接硬件给硬件发送指令以完成一些动作的蓝牙开发都是基于中心模式(central)模式的开发,也就是说我们开发的app是中心,我们要连接的硬件是外设。外设有个特点是它会一直广播自己,告诉中心我在这里,我在这里,快来连我---,这个时候呢,我们的中心就可以通过扫描发现并且点击连接了。
2,服务(service)和特征(character)
我们通过蓝牙连接了硬件,无非就是想通过连接硬件的读取一些信息,例如硬件通过温度传感器获取的环境温度,或者湿度传感器获取的环境湿度,又或者是硬件自己的电量等等。或者是给这个设备发送一些指令以达到控制硬件行为的目的,例如我们的app连接了空调,想发送指令过去改变空调的预设温度,或者发送指令给共享单车,让单车的锁解开。我们的app中心与设备之间的通信需要一个桥梁,这个桥梁就是服务与特征。一个设备中可能包含很多服务,而一个服务可能又包含几个特征。比方说,标准蓝牙模块会有几个内置的服务与特征如温度,心跳,等等。
这些服务与特征都是硬件工程师可以直接拿来用的。如果有其他特殊的需求,硬件工程师也可以再重新自己定义服务与特征。
3,读(read) , 写(write), 订阅(notify)
我们的目的是读取设备中的数据(read) , 或者给设备写入一定的数据(write)。有时候我们还想设备的数据变化的时候不需要我们手动去读取这个值,需要设备自动通知我们它的值变化了,值是多少。把值告诉app,这个时候就需要订阅这个特征了(notify)
二 : app做为中心(central)编码
鉴于大部分需求都是以app做为中心,硬件设备做为外设,所以这里只针对中心模式做详细介绍。
一般来讲,都要求我们的app时刻与硬件保持连接,当然了,这里的时刻保持连接指的是物理上的,我们的app能看到的是当前设备处于连接状态。低功耗蓝牙内部的实现绝对不是时刻保持连接,否则也不能称之为低功耗蓝牙了。Corebluetooth框架内部app做为中心的情况下蓝牙管理有一个中心类,叫CBCentralManager,我们叫它中心管理类。我们手机的蓝牙开启状态,手机与蓝牙设备等连接,断开连接,连接失败等等都由这个中心管理类来控制。为了这个中心管理类在app的生命周期都存在。我们一般把它进行封装,做成一个单例。这样app整个生命周期中这个单例时刻存在。下面是封装的蓝牙单例代码。
import Foundation
import CoreBluetooth
//用于看发送数据是否成功!
class LLBlueTooth:NSObject {
//单例对象
internal static let instance = LLBlueTooth()
//中心对象
var central : CBCentralManager?
//中心扫描到的设备都可以保存起来,
//扫描到新设备后可以通过通知的方式发送出去,连接设备界面可以接收通知,实时刷新设备列表
var deviceList: NSMutableArray?
// 当前连接的设备
var peripheral:CBPeripheral!
//发送数据特征(连接到设备之后可以把需要用到的特征保存起来,方便使用)
var sendCharacteristic:CBCharacteristic?
override init() {
super.init()
self.central = CBCentralManager.init(delegate:self, queue:nil, options:[CBCentralManagerOptionShowPowerAlertKey:false])
self.deviceList = NSMutableArray()
}
// MARK: 扫描设备的方法
func scanForPeripheralsWithServices(_ serviceUUIDS:[CBUUID]?, options:[String: AnyObject]?){
self.central?.scanForPeripherals(withServices: serviceUUIDS, options: options)
}
// MARK: 停止扫描
func stopScan() {
self.central?.stopScan()
}
// MARK: 写数据
func writeToPeripheral(_ data: Data) {
peripheral.writeValue(data , for: sendCharacteristic!, type: CBCharacteristicWriteType.withResponse)
}
// MARK: 连接某个设备的方法
/*
* 设备有几个状态
@available(iOS 7.0, *)
public enum CBPeripheralState : Int {
case disconnected
case connecting
case connected
@available(iOS 9.0, *)
case disconnecting
}
*/
func requestConnectPeripheral(_ model:CBPeripheral) {
if (model.state != CBPeripheralState.connected) {
central?.connect(model , options: nil)
}
}
}
//MARK: -- 中心管理器的代理
extension LLBlueTooth : CBCentralManagerDelegate{
// MARK: 检查运行这个App的设备是不是支持BLE。
func centralManagerDidUpdateState(_ central: CBCentralManager){
if #available(iOS 10.0, *) {
switch central.state {
case CBManagerState.poweredOn:
print("蓝牙打开")
case CBManagerState.unauthorized:
print("没有蓝牙功能")
case CBManagerState.poweredOff:
print("蓝牙关闭")
default:
print("未知状态")
}
}
// 手机蓝牙状态发生变化,可以发送通知出去。提示用户
}
// 开始扫描之后会扫描到蓝牙设备,扫描到之后走到这个代理方法
// MARK: 中心管理器扫描到了设备
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
// 在这个地方可以判读是不是自己本公司的设备,这个是根据设备的名称过滤的
guard peripheral.name != nil , peripheral.name!.contains("*****") else {
return
}
// 这里判断重复,加到devielist中。发出通知。
}
// MARK: 连接外设成功,开始发现服务
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral){
// 设置代理
peripheral.delegate = self
// 开始发现服务
peripheral.discoverServices(nil)
// 保存当前连接设备
self.peripheral = peripheral
// 这里可以发通知出去告诉设备连接界面连接成功
}
// MARK: 连接外设失败
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
// 这里可以发通知出去告诉设备连接界面连接失败
}
// MARK: 连接丢失
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
NotificationCenter.default.post(name: Notification.Name(rawValue: "DidDisConnectPeriphernalNotification"), object: nil, userInfo: ["deviceList": self.deviceList as AnyObject])
// 这里可以发通知出去告诉设备连接界面连接丢失
}
}
// 外设的代理
extension LLBlueTooth : CBPeripheralDelegate {
//MARK: - 匹配对应服务UUID
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?){
if error != nil {
return
}
for service in peripheral.services! {
peripheral.discoverCharacteristics(nil, for: service )
}
}
//MARK: - 服务下的特征
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?){
if (error != nil){
return
}
for characteristic in service.characteristics! {
switch characteristic.uuid.description {
case "A28DA977":
// 订阅特征值,订阅成功后后续所有的值变化都会自动通知
peripheral.setNotifyValue(true, for: characteristic)
case "******":
// 读区特征值,只能读到一次
peripheral.readValue(for:characteristic)
default:
print("扫描到其他特征")
}
}
}
//MARK: - 特征的订阅状体发生变化
func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?){
guard error == nil else {
return
}
}
// MARK: - 获取外设发来的数据
// 注意,所有的,不管是 read , notify 的特征的值都是在这里读取
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?)-> (){
if(error != nil){
return
}
switch characteristic.uuid.uuidString {
case "***************":
print("接收到了设备的温度特征的值的变化")
default:
print("收到了其他数据特征数据: \(characteristic.uuid.uuidString)")
}
}
//MARK: - 检测中心向外设写数据是否成功
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
if(error != nil){
print("发送数据失败!error信息:\(String(describing: error))")
}
}
}
如果有没有写清楚的地方,欢迎大家提问和补充。