iOS 蓝牙技术(swift也可使用)

蓝牙常见名称和缩写

  • MFI ======= make for ipad ,iphone, itouch 专们为苹果设备制作的设备
  • BLE ==== buletouch low energy,蓝牙4.0设备因为低耗电,所以也叫做BLE
  • peripheral,central == 外设和中心,发起连接的时central,被连接的设备为perilheral
  • service and characteristic === 服务和特征 每个设备会提供服务和特征,类似于服务端的api,但是机构不同。每个外设会有很多服务,每个服务中包含很多字段,这些字段的权限一般分为 读read,写write,通知notiy几种,就是我们连接设备后具体需要操作的内容。
  • Description 每个characteristic可以对应一个或多个Description用户描述characteristic的信息或属性
    MFI === 开发使用ExternalAccessory 框架
    4.0 BLE === 开发使用CoreBluetooth 框架

蓝牙基础知识

CoreBluetooth框架的核心其实是两个东西,peripheral和central, 可以理解成外设和中心。对应他们分别有一组相关的API和类


1442301709276906.png
  • 这两组api分别对应不同的业务场景,左侧叫做中心模式,就是以你的app作为中心,连接其他的外设的场景,而右侧称为外设模式,使用手机作为外设别其他中心设备操作的场景。
  • 服务和特征,特征的属性(service and characteristic):
    每个设备都会有一些服务,每个服务里面都会有一些特征,特征就是具体键值对,提供数据的地方。每个特征属性分为这么几种:读,写,通知这么几种方式。
//objcetive c特征的定义枚举
typedef NS_OPTIONS(NSUInteger, CBCharacteristicProperties) {
    CBCharacteristicPropertyBroadcast                                               = 0x01,
    CBCharacteristicPropertyRead                                                    = 0x02,
    CBCharacteristicPropertyWriteWithoutResponse                                    = 0x04,
    CBCharacteristicPropertyWrite                                                   = 0x08,
    CBCharacteristicPropertyNotify                                                  = 0x10,
    CBCharacteristicPropertyIndicate                                                = 0x20,
    CBCharacteristicPropertyAuthenticatedSignedWrites                               = 0x40,
    CBCharacteristicPropertyExtendedProperties                                      = 0x80,
    CBCharacteristicPropertyNotifyEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0)     = 0x100,
    CBCharacteristicPropertyIndicateEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0)   = 0x200
};

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

1442301743869760.png

蓝牙中心模式流程

  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)

蓝牙外设模式流程

  1. 启动一个Peripheral管理对象

  2. 本地Peripheral设置服务,特性,描述,权限等等

  3. Peripheral发送广告

  4. 设置处理订阅、取消订阅、读characteristic、写characteristic的委托方法

蓝牙设备状态

  1. 待机状态(standby):设备没有传输和发送数据,并且没有连接到任何设

  2. 广播状态(Advertiser):周期性广播状态

  3. 扫描状态(Scanner):主动寻找正在广播的设备

  4. 发起链接状态(Initiator):主动向扫描设备发起连接。

  5. 主设备(Master):作为主设备连接到其他设备。

  6. 从设备(Slave):作为从设备连接到其他设备。

蓝牙设备的五种工作状态

准备(standby)
广播(advertising)
监听扫描(Scanning
发起连接(Initiating)
已连接(Connected)

蓝牙和版本的使用限制

蓝牙2.0 === 越狱设备
蓝牙4.0 === iOS 6 以上
MFI认证设备(Make For ipod/ipad/iphone) === 无限制

XMBlueToothManager

XMBlueToothManager是对BabyBluetooth的二次封装,由于BabyBluetooth中链式函数的使用,导致不能在swift工程中使用,XMBlueToothManager完全弥补了这样的问题,全部使用block回调。代码实现起来方便好用。


IMG_2652.PNG
#import <Foundation/Foundation.h>
#import <CoreBluetooth/CoreBluetooth.h>
#import "BabyBluetooth.h"
#import "SVProgressHUD.h"

#define XMSERVICEBLOCK (CBPeripheral *peripheral, NSError *error)
#define XMCHARCTICBLOCK (CBPeripheral *peripheral, CBService *service, NSError *error)
#define XMVALUEFORCHARCTIC (CBPeripheral *peripheral, CBCharacteristic *characteristics, NSError *error)
#define XMDISCOVERDSCRIPFORCH (CBPeripheral *peripheral, CBCharacteristic *characteristic, NSError *error)
#define XMREADVALUEFORDES (CBPeripheral *peripheral, CBDescriptor *descriptor, NSError *error)
/**
 扫描到设备
 
 @param central 外设中心
 @param peripheral 外设
 @param advertisementData advertisementData
 @param RSSI RSSI
 */
typedef void(^XMPeripheralsBlock)(CBCentralManager *central, CBPeripheral *peripheral, NSDictionary *advertisementData, NSNumber *RSSI);

/**
 发现设备服务
 
 @param peripheral 外设
 @param error error
 */
typedef void(^XMDiscoverServices)XMSERVICEBLOCK;

/**
 发现设备的Characteristics
 
 @param peripheral peripheral
 @param service service
 @param error error
 */
typedef void(^XMDiscoverCharacteristics)XMCHARCTICBLOCK;

/**
 读取到的特征值
 
 @param peripheral peripheral
 @param characteristics characteristics
 @param error error
 */
typedef void(^XMReadValueForCharacteristic)XMVALUEFORCHARCTIC;

/**
 读取到特征描述
 
 @param peripheral peripheral
 @param characteristic characteristic
 @param error error
 */
typedef void(^XMDiscoverDescriptorsForCharacteristic)XMDISCOVERDSCRIPFORCH;

/**
 读取Descriptor
 
 @param peripheral peripheral
 @param descriptor descriptor
 @param error error
 */
typedef void(^XMReadValueForDescriptors)XMREADVALUEFORDES;

/**
 设置查找设备的过滤器
 
 @param peripheralName peripheralName
 @param advertisementData advertisementData
 @param RSSI RSSI
 */
typedef BOOL(^XMsetFilterOnDiscoverPeripherals)(NSString *peripheralName, NSDictionary *advertisementData, NSNumber *RSSI);

/**
 链接成功与否
 
 @param state state
 */
typedef void(^XMConnectStateBlock)(BOOL state);

/**
 断开连接
 
 @param central central
 @param peripheral peripheral
 @param error error
 */
typedef void(^XMDisconnectAtChannel)(CBCentralManager *central, CBPeripheral *peripheral, NSError *error);

/**
 发现服务
 */
typedef void(^XMDiscoverServicesAtChannel)XMSERVICEBLOCK;

/**
 发现特征
 */
typedef void(^XMDiscoverCharacteristicsAtChannel)XMCHARCTICBLOCK;

/**
 读取到service的特征值
 */
typedef void(^XMReadValueForCharacteristicAtChannel)XMVALUEFORCHARCTIC;

/**
 发现特征描述
 */
typedef void(^XMDiscoverDescriptorsForCharacteristicAtChannel)XMDISCOVERDSCRIPFORCH;

/**
 读取特定频道的描述信息
 */
typedef void(^XMReadValueForDescriptorsAtChannel)XMREADVALUEFORDES;
typedef void(^XMReadRSSI)(NSNumber *RSSI, NSError *error);
typedef void(^XMPeripheralManagerDidUpdateState)(CBPeripheralManager *peripheral);
typedef void(^XMDidReadRSSIAtChannel)(NSNumber *RSSI, NSError *error);
typedef void(^XMBlockOnDisconnectAtChannel)(CBCentralManager *central, CBPeripheral *peripheral, NSError *error);
typedef void(^XMDidUpdateNotificationStateForCharacteristicAtChannel)(CBCharacteristic *characteristic, NSError *error);
typedef void(^XMDidWriteValueForCharacteristicAtChannel)(CBCharacteristic *characteristic, NSError *error);
@protocol XMBlueToothDelegate <NSObject>


@end

@interface XMBlueToothManager : NSObject


@property (nonatomic , weak) id<XMBlueToothDelegate>delegate;
@property (nonatomic , strong) NSMutableArray *peripherals;//查找到的所有的外设
@property (nonatomic , copy) XMDidReadRSSIAtChannel readRSSIAtChannel;
@property (nonatomic , copy) XMBlockOnDisconnectAtChannel disconnectAtChannel;
@property (nonatomic , copy) XMDidUpdateNotificationStateForCharacteristicAtChannel notiUpdateAtChannel;
@property (nonatomic , copy) XMDidWriteValueForCharacteristicAtChannel didWriteData;
/**
 单例初始化
 */
+ (XMBlueToothManager *)defaultManager;

/**
 取消之前的连接
 */
- (void)cancleAllConnect;

/**
 开始扫描外设
 */
- (void)beginToScan;

/**
 扫描多久之后停止扫描
 
 @param time 限制时间(s)
 */
- (void)beginToScanWithLimitTime:(int)time;

/**
 停止扫描
 */
- (void)cancleScan;

/**
 扫描到外设的代理
 
 @param block block
 */
- (void)xm_discoverPeripherals:(XMPeripheralsBlock)block;

/**
 发现设备服务
 
 @param block block
 */
- (void)xm_discoverPeripheralService:(XMDiscoverServices)block;

/**
 发现Characteristic
 
 @param block block
 */
- (void)xm_discoverCharacteristics:(XMDiscoverCharacteristics)block;

/**
 读取特征值
 
 @param block block
 */
- (void)xm_readValueForCharacteristic:(XMReadValueForCharacteristic)block;

/**
 读取特征描述
 
 @param block block
 */
- (void)xm_discoverDescriptorsForCharacteristic:(XMDiscoverDescriptorsForCharacteristic)block;

/**
 读取Descriptors
 
 @param block block
 */
- (void)xm_readValueForDescriptors:(XMReadValueForDescriptors)block;

/**
 设置设备查找的过滤条件
 
 @param block block
 */
- (void)xm_setFilterOnDiscoverPeripherals:(XMsetFilterOnDiscoverPeripherals)block;

/**
 连接外设
 
 @param channel channel
 */
- (void)connectToPeripheralWithChannel:(NSString *)channel peripheral:(CBPeripheral *)peripheral;

/**
 连接成功与否
 
 @param block block
 */
- (void)xm_connectState:(XMConnectStateBlock)block;

/**
 断开连接失败的回调
 
 @param block block
 */
- (void)xm_disconnectAtChannelBlock:(XMDisconnectAtChannel)block;

/**
 发现服务
 
 @param block block
 */
- (void)xm_discoverServicesAtChannel:(XMDiscoverServicesAtChannel)block;

/**
 发现特征
 
 @param block block
 */
- (void)xm_xmDiscoverCharacteristicsAtChannel:(XMDiscoverCharacteristicsAtChannel)block;

/**
 读取特征值
 
 @param block block
 */
- (void)xm_readValueForCharacterAtChannel:(XMReadValueForCharacteristicAtChannel)block;

/**
 发现特定频道的特征描述
 
 @param block block
 */
- (void)xm_discoverDescriptorsForCharacteristicAtChannel:(XMDiscoverDescriptorsForCharacteristicAtChannel)block;

/**
 读取特定频道的特征描述
 
 @param block block
 */
- (void)xm_readValueForDescriptorsAtChannel:(XMReadValueForDescriptorsAtChannel)block;

/**
 读取RSSI
 
 @param block block
 */
- (void)xm_readRSSI:(XMReadRSSI)block;

/**
 外设状态更新
 
 @param block block
 */
- (void)xm_peripheralManagerDidUpdateState:(XMPeripheralManagerDidUpdateState)block;

/**
 添加断开重连的设备
 
 @param peripheral peripheral
 */
- (void)xm_addAutoReconnectPeripheral:(CBPeripheral *)peripheral;

/**
 读取特定频道的rssi
 
 @param block block
 */
- (void)xm_didReadRSSIAtChannel:(XMDidReadRSSIAtChannel)block;

/**
 读取详细信息
 
 @param channel chanel
 @param characteristic 当前的characteristic
 @param currPeripheral 当前的currPeripheral
 */
- (void)readDetailValueOfCharacteristicWithChannel:(NSString *)channel characteristic:(CBCharacteristic *)characteristic currPeripheral:(CBPeripheral *)currPeripheral;

/**
 外设断开连接的回调
 
 @param block block
 */
- (void)xm_blockOnDisconnectAtChannel:(XMBlockOnDisconnectAtChannel)block;

/**
 订阅状态发生改变
 
 @param block block
 */
- (void)xm_didUpdateNotificationStateForCharacteristicAtChannel:(XMDidUpdateNotificationStateForCharacteristicAtChannel)block;

/**
 写数据成功的回调
 
 @param block block
 */
- (void)xm_didWriteValueForCharacteristicAtChannel:(XMDidWriteValueForCharacteristicAtChannel)block;

/**
 给外设写入数据
 
 @param data data
 */
- (void)writeData:(NSData *)data ToPeripheral:(CBPeripheral *)peripheral forCharacteristic:(CBCharacteristic *)characteristic;

/**
 订阅数据
 
 @param peripheral peripheral
 @param characteristic characteristic
 */
- (void)xm_setNotifiyWithPeripheral:(CBPeripheral *)peripheral forCharacteristic:(CBCharacteristic *)characteristic block:(XMReadValueForCharacteristic)block;

/**
 发现当前连接的设备
 
 @return peripherals
 */
- (NSArray *)xmFindConnectedPeripherals;

/**
 取消订阅
 
 @param peripheral peripheral
 @param characteristic characteristic
 */
- (void)xm_cancleNotifyWith:(CBPeripheral *)peripheral characteristic:(CBCharacteristic *)characteristic;

扫描设备

import UIKit

class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {


    @IBOutlet weak var tableView: UITableView!
    var peripherals = NSMutableArray()
    var peripheralsAD = NSMutableArray()
    var manager = XMBlueToothManager()
    var dataSource = NSMutableArray()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        SVProgressHUD.showInfo(withStatus: "准备打开设备")
        self.peripherals = NSMutableArray.init()
        self.peripheralsAD = NSMutableArray.init()
        self.dataSource = NSMutableArray.init()
        self.manager = XMBlueToothManager.default()
        self.setDelegate()
    }
  
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(true)
        self.manager.cancleAllConnect()
        self.manager.beginToScan()
    }
    func setDelegate() {
       self.manager.xm_discoverPeripherals { (central, peripheral, advertisementData, RSSI) in
//        XMLog(message: (peripheral?.name,RSSI))
        self.insertTableView(peripheral: peripheral!, advertisementData: advertisementData! as NSDictionary)
        }
        self.manager.xm_setFilter { (peripheralName, advertisementData, RSSI) -> Bool in
            if peripheralName != nil{//设置过滤条件
              return true
            }else{
              return false
            }
        }
    }
    func insertTableView(peripheral:CBPeripheral,advertisementData:NSDictionary) -> Void {
        if !self.peripherals.contains(peripheral) {
            let indexPath = NSIndexPath.init(row: self.peripherals.count, section: 0)
            self.peripherals.add(peripheral)
            self.peripheralsAD.add(advertisementData)
            self.tableView.insertRows(at: [indexPath as IndexPath], with: .automatic)
        }
        self.tableView.reloadData()
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.peripherals.count
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        let peripheral = self.peripherals.object(at: indexPath.row) as! CBPeripheral
        let ad = peripheralsAD.object(at: indexPath.row) as! NSDictionary

        cell.accessoryType = .disclosureIndicator
        cell.selectionStyle = .none;
        var localName = String()
        if (ad.object(forKey: "kCBAdvDataLocalName") != nil) {
            localName = ad.object(forKey: "kCBAdvDataLocalName") as! String
        }else{
            localName = peripheral.name!
        }
        cell.textLabel?.text = localName
        cell.detailTextLabel?.text = "读取中...";
        if (ad.object(forKey: "kCBAdvDataServiceUUIDs") != nil) {
            let serviceUUIDs = ad.object(forKey: "kCBAdvDataServiceUUIDs") as! NSArray
            if serviceUUIDs.count > 0 {
                cell.detailTextLabel?.text = String.init(format: "%lu个services", serviceUUIDs.count)
            }else{
                cell.detailTextLabel?.text = "0个services"
            } 
        }
        
        return cell
    }
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        self.manager.cancleScan()
        let perVC = segue.destination as! PeripheralViewController
        perVC.manager = self.manager;
        let cell = sender as! UITableViewCell
        let indexPath = self.tableView.indexPath(for: cell) 
        let per = self.peripherals.object(at: (indexPath?.row)!) as! CBPeripheral
        perVC.currPeripher = per;
        perVC.chuanzhi(currentPeripheral: per)
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

连接设备

import UIKit
import CoreBluetooth
class PeripheralViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
    @IBOutlet weak var tableView: UITableView!
    var manager = XMBlueToothManager()
    var services = NSMutableArray()
    var currPeripher:CBPeripheral?
    let channel:String = "perpheral"
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        self.services = NSMutableArray.init();
        SVProgressHUD.showInfo(withStatus: "准备连接设备")
        let button = UIButton.init(type: .custom)
        button.frame = CGRect.init(x: 0, y: 0, width: 30, height: 30)
        button.setTitle("😸", for: .normal)
        button.setTitleColor(UIColor.black, for: .normal)
        self.navigationItem.rightBarButtonItem = UIBarButtonItem.init(customView: button)
        button.addTarget(self, action: #selector(buttonAction(_:)), for: .touchUpInside)
    }
    func buttonAction(_ sender:UIButton) -> Void {
        let array = self.manager.xmFindConnectedPeripherals()
        XMLog(message: array)
    }
    func chuanzhi(currentPeripheral:CBPeripheral) -> Void {
        XMLog(message: currentPeripheral)
        self.currPeripher  = currentPeripheral
        //连接外设
        self.manager.connectToPeripheral(withChannel: self.channel, peripheral: currentPeripheral)
        self.manager.xm_discoverServices { (peripheral, error) in
            for s:CBService in (peripheral?.services)! {
               self.insertSectionToTableView(service: s)
            }
        }
        self.manager.xm_xmDiscoverCharacteristics(atChannel: { (peripheral, service, error) in
            self.insertRowToTableView(service: service!)
        })
        self.manager.xm_readValueForCharacter { (peripheral, characteristics, error) in
            
        }
        self.manager.xm_readValueForDescriptors { (peripheral, descriptor, error) in
            
        }
        self.manager.xm_discoverDescriptorsForCharacteristic { (peripheral, characteristics, error) in
            
        }
        self.manager.xm_disconnect { (central, peripheral, error) in
            
        }
        self.manager.xm_connectState { (state) in
            XMLog(message: state)
        }
    }
    func insertSectionToTableView(service:CBService) -> Void {
        XMLog(message: service.uuid.uuidString)
        let info = XMPeripheralInfo.init()
        info.serviceUUID = service.uuid
        self.services.add(info)
        let indexSet = NSIndexSet.init(index: self.services.count - 1)
        self.tableView.insertSections(indexSet as IndexSet, with: .automatic)
    }
    func insertRowToTableView(service:CBService) -> Void{
        var sect:Int = -1
        
        for (index,item) in self.services.enumerated() {
            if ((item as! XMPeripheralInfo).serviceUUID == service.uuid) {
                sect = index;
            }
        }
        
        if sect != -1 {
            let info = self.services.object(at: sect) as! XMPeripheralInfo
            
            for (index,item) in (service.characteristics?.enumerated())! {
                info.characteristics.add(item as CBCharacteristic)
                let indexPath = NSIndexPath.init(row: index, section: sect)
                self.tableView.insertRows(at: [indexPath as IndexPath], with: .automatic)
            }
            
        }
        
        
    }
    func numberOfSections(in tableView: UITableView) -> Int {
        return self.services.count
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        let info = self.services.object(at: section) as! XMPeripheralInfo
        return info.characteristics.count
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let info = self.services.object(at: indexPath.section) as! XMPeripheralInfo
        let characteristic = info.characteristics.object(at: indexPath.row) as! CBCharacteristic
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell")
        cell?.textLabel?.text = String.init(format: "%@", characteristic.uuid.uuidString)
        cell?.detailTextLabel?.text = characteristic.description
        return cell!
    }
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 50
    }
    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let label = UILabel.init(frame: CGRect.init(x: 0, y: 0, width: 100, height: 50))
        let info = self.services.object(at: section) as! XMPeripheralInfo
        label.text = String.init(format: "%@", info.serviceUUID)
        label.backgroundColor = UIColor.black
        label.textColor = UIColor.white
        return label        
    }
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    


    // MARK: - Navigation

    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        // Get the new view controller using segue.destinationViewController.
        // Pass the selected object to the new view controller.
        let vc = segue.destination as! CharacteristicViewController
        let cell = sender as! UITableViewCell
        let indexPath = self.tableView.indexPath(for: cell)
        vc.currPeripheral  = self.currPeripher
        let info = self.services.object(at: (indexPath?.section)!) as! XMPeripheralInfo
        let characteristic = info.characteristics.object(at: (indexPath?.row)!) as! CBCharacteristic
        vc.characteristic = characteristic
        vc.mananger = self.manager
    
    }
}

读取数据和写入数据

import UIKit

class CharacteristicViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {

    @IBOutlet weak var tableView: UITableView!
    var mananger = XMBlueToothManager()
    var sect = NSMutableArray()
    var readValueArray = NSMutableArray()
    var descriptors = NSMutableArray()
    var characteristic:CBCharacteristic?
    var currPeripheral:CBPeripheral?
    let channel =  "CBCharacteristic"
    override func viewDidLoad() {
        super.viewDidLoad()
        self.sect = NSMutableArray.init(array: ["read value","write value","desc","properties"])
        self.readValueArray = NSMutableArray.init()
        self.descriptors = NSMutableArray.init()
        self.mananger.readDetailValueOfCharacteristic(withChannel: self.channel, characteristic: self.characteristic, currPeripheral: self.currPeripheral)
        self.managerDelegate()
        self.creatUI()
    }
    func creatUI() {
        let headView = UIView.init(frame: CGRect.init(x: 0, y: 64, width: KSCREEN_WIDTH, height: 100))
        headView.backgroundColor = UIColor.darkGray
        self.view.addSubview(headView)
        let array = [self.currPeripheral?.name,String.init(format: "%@", (self.characteristic?.uuid)!),self.characteristic?.uuid.uuidString]
        for (index,item) in array.enumerated() {
            let label = UILabel.init(frame: CGRect.init(x: 0, y: 30*index, width: Int(KSCREEN_WIDTH), height: 30))
            label.text = item
            label.backgroundColor = UIColor.white
            label.font = UIFont.systemFont(ofSize: 18)
            headView.addSubview(label)
        }
        self.tableView.frame = CGRect.init(x: 0, y: (120 + 64), width: KSCREEN_WIDTH, height: (KSCREEN_HEIGHT - 64.0 - 120))
        let timer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(read), userInfo: nil, repeats: true)
        timer.fire()
    }
    func read() {
        self.currPeripheral?.readRSSI()
    }
    
    /// 根据信号强度算距离
    ///
    /// - Parameter RSSI: 信号强度值
    /// - Returns: 距离
    func getDistanceWith(RSSI:NSNumber) -> CGFloat {
        let power:Float = abs(RSSI.floatValue - 49.0)/(10*4.0)
        return CGFloat(powf(10.0, power)/10.0)
    }
    func managerDelegate(){
        self.mananger.xm_readValueForCharacter { (peripheral, characteristics, error) in
            if ((characteristics?.value) != nil){
                let data = (characteristics?.value)! as Data
                let string = String.init(data: data, encoding: .utf8)
                if string != nil{
                    XMLog(message: "数据为空")
                }
                self.insertReadValues(characteristics: characteristics!)
            }
        }
        self.mananger.xm_discoverDescriptorsForCharacteristic { (peripheral, characteristic, error) in
            
            for item:CBDescriptor in (characteristic?.descriptors)!{
                self.insertDescriptor(descriptor: item)
            }
        }
        self.mananger.xm_readValueForDescriptors { (peripheral, descriptor, error) in
            for (index,item) in self.descriptors.enumerated(){
                if (item as! CBDescriptor) == descriptor{
                    let cell = self.tableView.cellForRow(at: IndexPath.init(row: index, section: 0))
                    cell?.detailTextLabel?.text = String.init(format: "%@", descriptor?.value as! CVarArg)
                }
            }
        }
        //订阅状态发生改变
        self.mananger.xm_didUpdateNotificationStateForCharacteristic { (characteristic, error) in
            XMLog(message: (characteristic?.uuid,(characteristic?.isNotifying)! ? "on" : "off"))
        }
        //实时读取外设的RSSI
        self.mananger.xm_didReadRSSI { (RSSI, error) in
            XMLog(message: ("setBlockOnDidReadRSSI:\(RSSI)"))
            let distance:CGFloat? = self.getDistanceWith(RSSI: RSSI!)
            XMLog(message: distance)
        }
        self.mananger.xm_readRSSI { (RSSI, error) in
            XMLog(message: RSSI)
        }
        self.mananger.xm_blockOnDisconnect { (central, peripheral, error) in
            XMLog(message: "连接失败")
        }
        //添加重连设备
        self.mananger.xm_addAutoReconnectPeripheral(self.currPeripheral)
        //写出值成功的回调
        self.mananger.xm_didWriteValueForCharacteristic { (characteristic, error) in
            if error == nil{
                SVProgressHUD.showInfo(withStatus: "写入成功啦")
            }
        }
    }
    func setNotifiy(sender:UIButton) {
        let btn = sender
        if self.currPeripheral?.state == .disconnected {
            SVProgressHUD.showInfo(withStatus: "peripheral已经断开连接,请重新连接")
            return
        }
        if (self.characteristic?.properties.contains(.notify))! || (self.characteristic?.properties.contains(.indicate))! {
            if (self.characteristic?.isNotifying)! {
                self.mananger.xm_cancleNotify(with: self.currPeripheral, characteristic: self.characteristic)
                btn.setTitle("通知", for: .normal)
            }else{
               self.currPeripheral?.setNotifyValue(true, for: self.characteristic!)
                self.mananger.xm_setNotifiy(with: self.currPeripheral, for: self.characteristic, block: { (peripheral, characteristic, error) in
                    if error == nil{
                        SVProgressHUD.showInfo(withStatus: "接收到订阅的数据")
                    }else{
                        SVProgressHUD.showError(withStatus: error as! String!)
                    }
                    self.insertReadValues(characteristics: characteristic!)
                })
                btn.setTitle("取消通知", for: .normal)
            }
        }else{
            SVProgressHUD.showInfo(withStatus: "这个characteristic没有nofity的权限")
        }
    }
    func insertReadValues(characteristics:CBCharacteristic) -> Void {
        var string:String?
        if (characteristics.value != nil) {
            string = String.init(data: characteristics.value!, encoding: .utf8)
        }
        if string == "" {
            print(string as Any)
        }
        self.readValueArray.add(string as Any)
        let indexPath = IndexPath.init(row: self.readValueArray.count-1, section: 0)
        self.tableView.insertRows(at: [indexPath as IndexPath], with: .automatic)
        self.tableView.scrollToRow(at: indexPath as IndexPath, at: .middle, animated: true)
    }
    func insertDescriptor(descriptor:CBDescriptor) -> Void {
        self.descriptors.add(descriptor)
        let indexPath = NSIndexPath.init(row: self.descriptors.count - 1, section: 2) as IndexPath
        self.tableView.insertRows(at: [indexPath], with: .automatic)
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell")
        switch indexPath.section {
        case 0:
            
          cell?.textLabel?.text = self.readValueArray.object(at: indexPath.row) as? String
          let formatter = DateFormatter.init()
          formatter.dateFormat = "yyyy-MM-dd hh:mm:ss"
          cell?.detailTextLabel?.text = formatter.string(from: NSDate.init() as Date)
            break
        case 1:
            cell?.textLabel?.text = "write a new value"
            break
        case 2:
            let descriptor = self.descriptors[indexPath.row] as! CBDescriptor
            cell?.textLabel?.text = String.init(format: "%@", descriptor.uuid)
            break
        case 3:
            let p = self.characteristic?.properties
            cell?.textLabel?.text = ""
            if (p?.contains(.broadcast))! {
                cell?.textLabel?.text = cell?.textLabel?.text?.appending(" | Broadcast")
            }
            if (p?.contains(.read))! {
                cell?.textLabel?.text = cell?.textLabel?.text?.appending(" | Read")
            }
            if (p?.contains(.writeWithoutResponse))! {
                cell?.textLabel?.text = cell?.textLabel?.text?.appending(" | WriteWithoutResponse")
            }
            if (p?.contains(.write))! {
                cell?.textLabel?.text = cell?.textLabel?.text?.appending(" | Write")
            }
            if (p?.contains(.notify))! {
                cell?.textLabel?.text = cell?.textLabel?.text?.appending(" | Notify")
            }
            if (p?.contains(.indicate))! {
                cell?.textLabel?.text = cell?.textLabel?.text?.appending(" | Indicate")
            }
            if (p?.contains(.authenticatedSignedWrites))! {
                cell?.textLabel?.text = cell?.textLabel?.text?.appending(" | AuthenticatedSignedWrites")
            }
            if (p?.contains(.extendedProperties))! {
                cell?.textLabel?.text = cell?.textLabel?.text?.appending(" | ExtendedProperties")
            }
            
            break
        default:
            break
        }
        return cell!
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        switch section {
        case 0:
            return self.readValueArray.count
        case 1:
            return 1
        case 2:
            return self.descriptors.count
        case 3:
            return 1
        default:
            return 0
        }
    }
    func numberOfSections(in tableView: UITableView) -> Int {
        return self.sect.count
    }
    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        switch section {
        case 1:
            let view = UIView.init(frame: CGRect.init(x: 0, y: 0, width: KSCREEN_WIDTH, height: 30))
            view.backgroundColor = UIColor.darkGray
            let title = UILabel.init(frame: CGRect.init(x: 0, y: 0, width: 100, height: 30))
            title.text = self.sect[section] as? String
            title.textColor = UIColor.white
            view.addSubview(title)
            let notiButton = UIButton.init(type: .custom)
            notiButton.frame = CGRect.init(x: 100, y: 0, width: 100, height: 30)
            notiButton.setTitle((self.characteristic?.isNotifying)! ?"取消通知":"通知", for: .normal)
            notiButton.backgroundColor = UIColor.darkGray
            notiButton.addTarget(self, action: #selector(setNotifiy(sender:)), for: .touchUpInside)
            if (self.characteristic?.isNotifying)! {
                self.mananger.xm_setNotifiy(with: self.currPeripheral, for: self.characteristic, block: { (peripheral, characteristics, error) in
                    self.insertReadValues(characteristics: characteristics!)
                })
            }
            view.addSubview(notiButton)
            let writeButton = UIButton.init(type: .custom)
            writeButton.frame = CGRect.init(x: 200, y: 0, width: 100, height: 30)
            writeButton.setTitle("写(0x01)", for: .normal)
            writeButton.backgroundColor = UIColor.darkGray
            writeButton.addTarget(self, action: #selector(writeValue), for: .touchUpInside)
            view.addSubview(writeButton)
            return view
        default:
            let label = UILabel.init(frame: CGRect.init(x: 0, y: 0, width: 100, height: 30))
            label.text = self.sect[section] as? String
            label.textColor = UIColor.white
            label.backgroundColor = UIColor.darkGray
            return label
        }
    }
    func writeValue(){
//        写入五位随机数
        let num = arc4random()%100000
        let string = String.init(format: "%d", num)
        let data = string.data(using: .utf8)
        self.mananger.write(data, to: self.currPeripheral, for: self.characteristic)
    }
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 30
    }
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
    }

Demo下载地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

  • 本文主要以蓝牙4.0做介绍,因为现在iOS能用的蓝牙也就是只仅仅4.0的设备 用的库就是core bluetoot...
    暮雨飞烟阅读 752评论 0 2
  • (一) iOS蓝牙开发蓝牙相关基础知识 蓝牙常见名称和缩写 MFI ======= make for ipad ...
    雷鸣1010阅读 4,941评论 2 12
  • 这里我们具体说明一下中心模式的应用场景。主设备(手机去扫描连接外设,发现外设服务和属性,操作服务和属性的应用。一般...
    丶逝水流年阅读 2,128评论 3 4
  • 今天点开简书居然看到有两条消息,由于工作忙碌,感觉每日只看不写已经很久了,前些时日的信誓旦旦变成了今日的惭愧无比,...
    缘柳阅读 227评论 0 0
  • 因为在二十几岁的年纪里,我们要做的从来都不是征战天下,一生戎马,而是在细微的尘事里按捺住躁动的情绪,变得沉稳,并且...
    仲念念阅读 685评论 6 15