BLE我爱编程

iOS 低功耗蓝牙(BLE)的IoT应用之 - 温度检测仪(上)

2017-02-18  本文已影响371人  梁睿坤

背景知识

蓝牙技术最初由爱立信(也就是多年前手机做得最丑最奇葩的公司,最终被用户以脚投票踢出市场)创制。技术始于爱立信公司的1994方案,它是研究在移动电话和其他配件间进行低功耗、低成本无线通信连接的方法。发明者希望为设备间的通讯创造一组统一规则(标准化协议),以解决用户间互不兼容的移动电子设备。1997年前爱立信公司以此概念接触了移动设备制造商,讨论其项目合作发展,结果获得支持。

1998年5月20日,索尼爱立信、国际商业机器、诺基亚等业界龙头创立“特别兴趣小组”(Special Interest Group,SIG),即[蓝牙技术联盟的前身,目标是开发一个成本低、效益高、可以在短距离范围内随意无线连接的蓝牙技术标准。

这项无线技术的名称取自古代丹麦维京国王Harald Blåtand的名字,他以统一了因宗教战争和领土争议而分裂的挪威与丹麦而闻名于世,而这个名字的英文便是Harald Bluetooth。

1998年时蓝牙推出0.7规格,支持Baseband与LMP(Link Manager Protocol)通讯协定两部分。1999年推出先后0.8版,0.9版、1.0 Draft版,1.0a版、1.0B版。1.0 Draft版,完成SDP(Service Discovery Protocol)协定、TCS(Telephony Control Specification)协定。1999年7月26日正式公布1.0版,确定使用2.4GHz频谱,最高资料传输速度1Mbps,同时开始了大规模宣传。和当时流行的红外线技术相比,蓝牙有着更高的传输速度,而且不需要像红外线那样进行接口对接口的连接,所有蓝牙设备基本上只要在有效通讯范围内使用,就可以进行随时连接。

当1.0规格推出以后,蓝牙并未立即受到广泛的应用,除了当时对应蓝牙功能的电子设备种类少,蓝牙装置也十分昂贵。2001年的1.1版正式列入IEEE标准,Bluetooth 1.1即为IEEE 802.15.1。同年,SIG成员公司超过2000家。过了几年之后,采用蓝牙技术的电子装置如雨后春笋般增加,售价也大幅下降。为了扩宽蓝牙的应用层面和传输速度,SIG先后推出了1.2、2.0版,以及其他附加新功能,例如EDR(Enhanced Data Rate,配合2.0的技术标准,将最大传输速度提高到3Mbps)、A2DP(Advanced Audio Distribution Profile,一个控音轨分配技术,主要应用于立体声耳机、AVRCP(A/V Remote Control Profile)等。Bluetooth 2.0将传输率提升至2Mbps、3Mbps,远大于1.x版的1Mbps(实际约723.2kbps)。

蓝牙从1.0到2.0其速度与传输距离一直是其硬伤,使用体验极差文件型的数据传输几乎等于不可用,所以我一直对蓝牙设备不感冒。直至遇到BLE的出现也就是低功耗蓝牙,来看看4.0之后的蓝牙有何技术特性吧:

蓝牙4.0

蓝牙4.0是Bluetooth SIG于2010年7月7日推出的新的规范。其最重要的特性是支持省电

蓝牙4.1

蓝牙4.1是蓝牙技术联盟于2013年底推出的新的规范,其目的是为了让 Bluetooth Smart 技术最终成为物联网(Internet of Things)发展的核心动力。
此版本为蓝牙4.0的软件更新版本,搭载蓝牙4.0设备的终端可通过软件更新获得此版本。

对于开发人员而言,该更新是蓝牙技术发展史上一项重要的进步。该更新提供了更高的灵活性和掌控度,让开发人员能创造更具创新并催化物联网(IOT)发展的产品。
支持多设备连接。

蓝牙4.2

蓝牙4.2是蓝牙技术联盟于2014年12月推出的新的规范。

蓝牙5

蓝牙5在2016年6月被宣布。在有效传输距离上将是4.2LE版本的4倍(理论上可达300米),传输速度将是4.2LE版本的2倍(速度上限为24Mbps)。蓝牙5.0还支持室内定位导航功能(结合WiFi可以实现精度小于1米的室内定位),允许无需配对接受信标的数据(比如广告、Beacon、位置信息等,传输率提高了8倍),针对物联网进行了很多底层优化。

看完这些特性除了不能上网以外其它的无线能力几乎都能秒杀WIFI了。

蓝牙常见名称和缩写

在进入IOS之前我们得深入到的理论领域,了解蓝牙4.0中的一些基本的术语:

外设、服务、特征间的关系

下图你在Apple的开发者网站上也能找到:

外设、服务、特征间的关系

蓝牙中心模式流程

  1. 建立中心角色
  2. 扫描外设(discover)
  3. 连接外设(connect)
  4. 扫描外设中的服务和特征(discover)
    4.1 获取外设的services
    4.2 获取外设的Characteristics,获取Characteristics的值,获取Characteristics的Descriptor和Descriptor的值
  5. 与外设做数据交互(explore and interact)
  6. 订阅Characteristic的通知
  7. 断开连接(disconnect)

蓝牙设备工作的状态

蓝牙和版本的使用限制

iOS 的实现

先创建一个 Swift 的工程,然后引入CoreBluetooth ,它就是iOS中提供蓝牙通信的核心库。然后ViewController必须实现 CBCentralManagerDelegate,CBPeripheralDelegate 两个接口:

import CoreBluetooth
import UIKit

class ViewController: UIViewController, CBCentralManagerDelegate,CBPeripheralDelegate {
        override func viewDidLoad() {
            super.viewDidLoad()
        }
} 

1.) 建立中心角色

然后要定义一个管理中心的对象变量,它就是蓝牙控制的主入口,然后在viewDidLoad()中实例化它:

class ViewController: UIViewController, CBCentralManagerDelegate,CBPeripheralDelegate {
    var manager : CBCentralManager!        
    override func viewDidLoad() {
            super.viewDidLoad()
            manager = CBCentralManager.init(delegate: self, queue: DispatchQueue.main)
        }
} 

2.) 扫描外设

管理中心一但被初始化后就会检查当前手机上的蓝牙状态,并调用 centralManagerDidUpdateState 方法,假设完整初始化后就马上进行设备扫描,那就要在centralManagerDidUpdateState中进行,在 viewDidLoad下方加入以下的代码:

 func centralManagerDidUpdateState(_ central: CBCentralManager) {
        switch central.state {
        case .unknown:
            print("未知的蓝牙设备")
        case .resetting:
            print("蓝牙正在被重置中")
        case .unsupported:
            print("不支持蓝牙服务")
        case .unauthorized:
            print("蓝牙服务未被授权")
        case .poweredOff:
            print("蓝牙服务已关闭")
        case .poweredOn:
            print("蓝牙已启动,开始扫描...")
            startScan()
        }
    }

 /// 扫描蓝牙设备
  func startScan() {
        manager.scanForPeripherals(withServices: nil, options: nil)
  }

注:只有返回.poweredOn状态下才能进行蓝牙设备的扫描。

scanForPeripherals方法就是对可发现的蓝牙设备进行扫描,输入的参数可以作一些过滤条件,我这里是不加任何的过滤条件无差别化地扫描。

3.) 连接外设

scanForPeripherals方法被调用后,CoreBluetooth就会调用centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber),所以我们就得在这个方法内将我们的目标蓝牙设备找出来,然后用一个变量Hold住它,否则这个外设就会被释放掉,然后你就会在XCode中得到一条提示信息说你没有Hold住你需要的设备变量了。

// 定义一个变量来Hold住目标设备
var connectedPeripheral : CBPeripheral!

func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
            if (peripheral.name=="BT05") {
                manager.connect(peripheral, options:nil)
                // 找到的设备必须持有它,否则CBCentralManager中也不会保存peripheral,那么CBPeripheralDelegate中的方法也不会被调用!
                connectedPeripheral = peripheral
                print("正在连接:\(peripheral.name)...")
            }
}

由于我不知道我的设备的ServiceUUID是什么,为了方便我编码的需要所以我只找一个名为"BT05"的设备名称(这是我蓝牙模块的默认名字)找到之后就马上调用connect进行连接,注意:当中心对象不停止扫描(manager.stopScan())以上的方法就会不断地被调用

一个主设备最多能连7个外设,每个外设最多只能给一个主设备连接,连接成功,失败,断开会进入各自的委托

func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral)
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?)
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?)

下文就会补充这几个委托方法的实现

4.1) 获取外设的服务

当连接成功后 centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) 就会被调用,在这里我们会设置找到的外设对象的委托(self),通常这个方法内会做另一种筛选那就是服务特征,通俗点说就是这个蓝牙设备可以提供些啥服务,例如写入,读取,或者其它什么的一些动作。

    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral){        
        print("正在查找Service")
        // 外设寻找目标服务
        peripheral.discoverServices(nil)
        peripheral.delegate = self
        self.title = peripheral.name
        // 停止扫描
        manager.stopScan()
    }

4.2) 获取外设特征

discoverServices(nil)传入了nil 那么就啥服务信息都直接获取,然后对服务进行发现,这个过程和前文中的设备发现非常的像

    // 这个服务地址可以从设备中查到的
    let ServiceUUID =  "FFE0"
    //扫描到Services
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?){
        if (error != nil){
            print("查找 services 时 \(peripheral.name) 报错 \(error?.localizedDescription)")
        }
        
        for service in peripheral.services! {
            // 需要连接的 CBCharacteristic 的 UUID
            if service.uuid.uuidString == ServiceUUID {
                peripheral.discoverCharacteristics(nil, for: service)
            }
        }
    }

5) 与外设做数据交互

这样我们实现的CBPeripheralDelegate委托接口中的peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?)将会被调用,用于读取蓝牙传过来的数据。

   // 同理我们要Hold住特征变量,否则会被释放掉
    var savedCharacteristic : CBCharacteristic!
    var lastString : NSString!

   // Interface Build 中的标签对象,用来显示从传感器读取的室温
   @IBOutlet weak var �lbTemperature: UILabel!
    //获取的charateristic的值,处理收接到的数据
    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        // 好了,charcteristic.value 就是从蓝牙设备传过来的原数据内容,我们可以在这里进行处理
        let resultStr = NSString(data: characteristic.value!, encoding: String.Encoding.utf8.rawValue)
        print(resultStr)
        
        // 将温度显示到标签中
        lbTemperature.text = resultStr
        
        if lastString == resultStr {
            return;
        }

        self.savedCharacteristic = characteristic
    }

6) 订阅 Characteristic 的通知

由于BLE4.0的"减弱式呼吸"工作特性我们需要对外设置发出的通知进行订阅,如数据发生改变中心可以第一时间获知。

    /// 扫描到 characteristic 时
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error? {
        if error != nil {
            print("查找 characteristics 时 \(peripheral.name) 报错 \(error?.localizedDescription)")
        }
        
        //获取Characteristic的值,读到数据会进入方法:
        for characteristic in service.characteristics! {
            peripheral.readValue(for: characteristic)
            //设置 characteristic 的 notifying 属性 为 true , 表示接受广播
            peripheral.setNotifyValue(true, for: characteristic)
        }
    }

7) 断开连接

其实到此这个App就完成了,以下的这些代码是对相关的错误处理进行补全:

    /// 连接到Peripherals-失败
    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?){
        print("连接到名字为 \(peripheral.name) 的设备失败,原因是 \(error?.localizedDescription)")
        
    }
    
    /// 断开
    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?){
        print("连接到名字为 \(peripheral.name) 的设备断开,原因是 \(error?.localizedDescription)"
        let alertView = UIAlertController.init(title: "抱歉", message: "蓝牙设备\(peripheral.name)连接断开,请重新扫描设备连接", preferredStyle: UIAlertControllerStyle.alert)
        alertView.show(self, sender: nil)
        
    }

   func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?){
        if error != nil{
            print("写入 characteristics 时 \(peripheral.name) 报错 \(error?.localizedDescription)")
        }
        // 这里可以处理向设备写入数据时的回调
    }

在下一篇中将会介绍蓝牙设备与Arduino 一端关于硬件部分与固件部分的制作。

上一篇 下一篇

猜你喜欢

热点阅读