在开发SaaS的过程中,小票打印的功能主要是通过App来跟打印机进行连接,并使用打印机指令对订单信息进行排版和打印的,在进行蓝牙开发的过程中,首先先要链接的就是蓝牙相关的基础知识。下面我主要介绍下蓝牙的基础知识和蓝牙连接的一些流程。
一、蓝牙版本和使用限制
蓝牙2.0为传统蓝牙,传统蓝牙也称为经典蓝牙.
使用限制:蓝牙2.0,不上架,使用私有API,手机需要越狱.
蓝牙4.0因为低耗电,所以也叫做低功耗蓝(BLE).它将三种规格集一体,包括传统蓝牙技术、高速技术和低耗能技术.
使用限制:iOS6以上,使用CoreBluetooth框架,手机不需要越狱.(CoreBluetooth是基于BLE来开发的)所以我们主要是基于蓝牙4.0版本使用CoreBluetooth框架进行开发
二、蓝牙的基础知识
CoreBluetooth框架的核心其实是两个东西,peripheral和central, 可以理解成外设和中心。对应他们分别有一组相关的API和类
这两组api分别对应不同的业务场景,左侧叫做中心模式,就是以你的app作为中心,连接其他的外设的场景,而右侧称为外设模式,使用手机作为外设别其他中心设备操作的场景。(在开发过程中我们主要是使用中心模式,以App为中心,来扫描周围的设备,然后进行连接)
服务和特征,特征的属性(service and characteristic):
一个外设包含多个服务,而每一个服务中又包含多个特征,特征包括特征的值和特征的描述.每个服务包含多个字段,字段的权限有read(读)、write(写)、notify(通知).
三、蓝牙中心模式流程
1、建立中心角色 [[CBCentralManager alloc] initWithDelegate:self queue:nil]
2、扫描外设 scanForPeripherals
3、发现设备 didDiscoverPeripheral
4、连接外设 connectPeripheral
连接失败 didFailToConnectPeripheral
连接断开 didDisconnectPeripheral
连接成功 didConnectPeripheral
5、扫描外设中的服务 discoverServices
发现并获取外设中的服务 `didDiscoverServices`
6、扫描外设对应服务的特征 discoverCharacteristics
发现并获取外设对应服务的特征 `didDiscoverCharacteristicsForService`
给对应特征写数据 `writeValue:forCharacteristic:type:`
7、订阅特征的通知 setNotifyValue:forCharacteristic:
根据特征读取数据 `didUpdateValueForCharacteristic`
8、断开连接 centralManager: didDisconnectPeripheral
四、蓝牙连接外设的实现流程
1、打印采用中心模式
导入CoreBluetooth框架,#import
2、遵守CBCentralManagerDelegate,CBPeripheralDelegate
协议
实现下面方法
- (void)centralManagerDidUpdateState:(nonnull CBCentralManager *)central{}
3、添加属性
// 中心管理者(管理设备的扫描和连接)
@property (nonatomic, strong) CBCentralManager *manager;
// 存储扫描到的设备
@property (nonatomic, strong) NSMutableArray *peripherals;
// 可写入数据的特性
@property (nonatomic, strong) NSMutableArray *writeChatacters;
//当前连接的外设
@property (nonatomic, strong) CBPeripheral *connectedPerpheral;
//当前蓝牙设备的状态
@property (nonatomic, assign) CBManagerState peripheralState;
4、创建中心管理者
//初始化并设置委托和线程队列,一个线程的参数可以为nil,默认会就main线程
self.manager = [[CBCentralManager alloc] initWithDelegate:self queue:dispatch_get_main_queue()];
初始化设备存储数组
- (NSMutableArray *)peripherals {
if (!_peripherals) {
self.peripherals = [NSMutableArray array];
}
return _peripherals;
}
初始化可写入数据特征数组
- (NSMutableArray *)writeChatacters {
if (!_writeChatacters) {
self.writeChatacters = [NSMutableArray array];
}
return _writeChatacters;
}
5、扫描外设(discover),扫描外设的方法我们放在centralManager成功打开的委托中,因为只有设备成功打开,才能开始扫描,否则会报错。
//获取中心管理者的状态
- (void)centralManagerDidUpdateState:(nonnull CBCentralManager *)central
{
switch (central.state) {
case CBManagerStateUnknown:{
NSLog(@"未知状态");
self.peripheralState = central.state;
}
break;
case CBManagerStateResetting:
{
NSLog(@"重置状态");
self.peripheralState = central.state;
}
break;
case CBManagerStateUnsupported:
{
NSLog(@"不支持的状态");
self.peripheralState = central.state;
}
break;
case CBManagerStateUnauthorized:
{
NSLog(@"未授权的状态");
self.peripheralState = central.state;
}
break;
case CBManagerStatePoweredOff:
{
NSLog(@"关闭状态");
self.peripheralState = central.state;
}
break;
case CBManagerStatePoweredOn:
{
NSLog(@"开启状态-可用状态");
self.peripheralState = central.state;
//蓝牙状态打开,开始扫描外设,第一个参数nil就是扫描周围所有的外设
[_manager scanForPeripheralsWithServices:nil options:nil];
}
break;
default:
break;
}
}
6、扫描到设备并开始连接
/** 扫描到设备会进入方法
@param central 中心管理者
@param peripheral 扫描到的设备
@param advertisementData 广告信息
@param RSSI 信号强度
*/
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral
advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI {
// 当设备名称开头为P的时候并且没有被添加到数组中的时候添加到设备存储中
if ([peripheral.name hasPrefix:@"P"] && ![self.peripherals containsObject:peripheral]) {
//找到的设备必须持有它,否则CBCentralManager中也不会保存peripheral,那么CBPeripheralDelegate中的方法也不会被调用!!
[self.peripherals addObject:peripheral];
//自动连接设备
[_manager connectPeripheral:peripheral options:nil];
}
}
7、连接的三种状态,如果连接成功,则扫描所有服务(也可以扫描指定服务)
连接失败重连
/** 连接失败
@param central 中心管理者
@param peripheral 连接失败的设备
@param error 错误信息
*/
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
NSLog(@">>>连接到名称为(%@)的设备-失败,原因:%@",[peripheral name],[error localizedDescription]);
}
连接断开重连
/** 连接断开
@param central 中心管理者
@param peripheral 连接断开的设备
@param error 错误信息
*/
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
NSLog(@">>>外设连接断开连接 %@: %@\n", [peripheral name], [error localizedDescription]);
}
连接成功并扫描服务
/** 连接成功
@param central 中心管理者
@param peripheral 连接成功的设备
*/
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
NSLog(@">>>连接到名称为(%@)的设备-成功",[peripheral.name](http://peripheral.name/));
self.connectedPerpheral = peripheral;
// 设置设备的代理
peripheral.delegate = self;
// 扫描外设Services:传入nil代表扫描所有服务,成功后会进入方法:-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{}
[peripheral discoverServices:nil];
}
8、发现服务并扫描服务对应的特征
/** 扫描到服务
@param peripheral 服务对应的设备
@param error 扫描错误信息
*/
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
// NSLog(@">>>扫描到服务:%@",peripheral.services);
if (error) {
NSLog(@">>>Discovered services for %@ with error: %@", peripheral.name, [error localizedDescription]);
return;
}
// 遍历所有的服务
for (CBService *service in peripheral.services)
{
NSLog(@"服务:%@",service.UUID.UUIDString);
//扫描每个service的Characteristics,扫描到后会进入方法: -(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{}
[peripheral discoverCharacteristics:nil forService:service];
}
}
9、扫描到对应的特征,并存储
/** 扫描到对应的特征
@param peripheral 设备
@param service 特征对应的服务
@param error 错误信息
*/
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
if (error) {
NSLog(@"error Discovered characteristics for %@ with error: %@", service.UUID, [error localizedDescription]);
return;
}
for (CBCharacteristic *characteristic in service.characteristics) {
NSLog(@"service:%@ 的 Characteristic: %@",service.UUID,characteristic.UUID);
//只有 characteristic.properties 有write的权限才可以写
if(characteristic.properties & CBCharacteristicPropertyWrite){
NSDictionary *dic = @{@"character":characteristic,@"type":@(CBCharacteristicWriteWithResponse)};
[self.writeChatacters addObject:dic];
}
}
}
10、把数据写到Characteristic中和写入回调
//写数据
-(void)writeCharacteristic:(CBPeripheral *)peripheral characteristic:(CBCharacteristic *)characteristic value:(NSData *)value {
//打印出 characteristic 的权限,可以看到有很多种,这是一个NS_OPTIONS,就是可以同时用于好几个值,常见的有read,write,notify,indicate,知知道这几个基本就够用了,前连个是读写权限,后两个都是通知,两种不同的通知方式。
/*
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
};
*/
NSLog(@"%lu", (unsigned long)characteristic.properties);
//只有 characteristic.properties 有write的权限才可以写
if(characteristic.properties & CBCharacteristicPropertyWrite){
/* 最好一个type参数可以为CBCharacteristicWriteWithResponse或type:CBCharacteristicWriteWithResponse,区别是是否会有反馈 */
[peripheral writeValue:value forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
} else {
NSLog(@"该字段不可写!");
}
}
//写入数据的回调
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error
{
if (error) {
NSLog(@"发送失败");
} else {
NSLog(@"已成功发送至蓝牙设备");
}
}
11、断开连接(disconnect)
//停止扫描并断开连接
-(void)disconnectPeripheral:(CBCentralManager *)centralManager peripheral:(CBPeripheral *)peripheral {
//停止扫描
[centralManager stopScan];
//断开连接
[centralManager cancelPeripheralConnection:peripheral];
}
在需要打印的时候通过已经存储的数据特征来调用数据的写入进行打印。
后续会汇总小票打印的一些主要功能
参考
ios蓝牙开发(二)ios连接外设的代码实现
https://github.com/coolnameismy/BabyBluetooth