iOS开发之蓝牙/Socket链接小票打印机(二)
上一篇 主要介绍了部分ESC/POS指令集,包括一些常用的排版指令,打印位图指令等。另外,还介绍了将图片转换成点阵图的方法。在这篇文章中,将主要介绍通过蓝牙和Socket连接打印机,发送打印指令相关知识。这里将用到 CoreBluetooth.framework 和 CocoaAsyncSocket 。
从策划到设计制作,每一步都追求做到细腻,制作可持续发展的企业网站。为客户提供成都网站建设、网站设计、网站策划、网页设计、域名注册、网页空间、网络营销、VI设计、 网站改版、漏洞修补等服务。为客户提供更好的一站式互联网解决方案,以客户的口碑塑造优易品牌,携手广大客户,共同发展进步。
蓝牙是一种支持设备间短距离通讯的无线电技术。iOS系统中,有四个框架支持蓝牙链接:
CoreBluetooth框架有两个核心概念,central(中心)和 peripheral(外设),它们分别有自己对应的API;这里显然是手机作为central,蓝牙打印机作为peripheral;
设置代理后,会回调此方法,确认蓝牙状态,当状态为 CBCentralManagerStatePoweredOn 才能去扫描设备,蓝牙状态变化时,也会回调此方法
调用此方法开始扫描外设
注意:第一个参数指定一个 CBUUID 对象数组,每个对象表示外围设备正在通告的服务的通用唯一标识符(UUID)。此时,仅返回公布这些服务的外设。当参数为 nil ,则返回所有已发现的外设,而不管其支持的服务是什么。
当扫描到4.0外设后会回调此方法,这里包含设备的相关信息,如名称、UUID、信号强度等;
调用此方法连接外设
[self.centralManager connectPeripheral:peripheral options:nil];
注意:第一个参数是要连接的外设。第二个参数 options 是可选的 NSDictionary ,系统定义了一下三个键,它们的值都是NSNumber (Boolean);默认为NO。当设置为YES,则应用进入后台或者被挂起后,系统会用Alert通知蓝牙外设的状态变化,效果是这样
连接成功或失败,都有对应的回调方法
连接成功后设置代理 peripheral.delegate = self ,调用 [peripheral discoverServices:nil]; 寻找外设内的服务。这里的参数是一个存放 CBUUID 对象的数组,用于发现特定的服务。当传nil时,表示发现外设内所有的服务。发现服务后系统会回调下面的方法:
发现服务后,调用 [peripheral discoverCharacteristics:nil forService:service]; 去发现服务中包含的特征。和上面几个方法一样,第一个参数用于发现指定的特征。为nil时,表示发现服务的所有特征。
当扫描到写入特征时,保存,用于写入数据。
写入数据,我们只需要调用方法
这里的 self.peripheral 就是连接的外设, self.characteristicInfo 就是之前保存的写入特征;这里最好使用 CBCharacteristicPropertyWrite 特征,并且 type 选择 CBCharacteristicWriteWithResponse 。当写入数据成功后,系统会通过下面这个方法通知我们:
由于蓝牙设备每次可写入的数据量是有限制的,因此,我们需要将之前拼接的打印数据进行拆分,分批发送给打印机
这里的 MAX_CHARACTERISTIC_VALUE_SIZE 是个宏定义,表示每次发送的数据长度,经笔者测试,当 MAX_CHARACTERISTIC_VALUE_SIZE = 20 时,打印文字是正常速度。但打印图片的速度非常慢, 应该在硬件允许的范围内,每次发尽量多的数据。 不同品牌型号的打印机,这个参数是不同的,笔者的蓝牙打印机该值最多到140。超出后会出现无法打印问题。 最后笔者将该值定为 MAX_CHARACTERISTIC_VALUE_SIZE = 120 ,测试了公司几台打印机都没有问题。
另外iOS9以后增加了方法 maximumWriteValueLengthForType: 可以获取写入特诊的最大写入数据量,但经笔者测试,对于部分打印机(比如我们公司的)是不准确的,因此,不要太依赖此方法,最好还是自己取一个合适的值。
注意:每个打印机都有一个缓冲区,缓冲区的大小视品牌型号有所不同。打印机的打印速度有限,如果我们瞬间发送大量的数据给打印机,会造成打印机缓冲区满。缓冲区满后,如继续写入,可能会出现数据丢失,打印乱码。
这里使用 CocoaAsyncSocket 开源框架,与打印机进行 Socket 连接。 CocoaAsyncSocket 中主要包含两个类:
这里我们只用到 GCDAsyncSocket ,因此只需要将 GCDAsyncSocket.h 和 GCDAsyncSocket.m 两个文件导入项目。
注意:手机和打印机必须在同一局域网下,设置到打印机的host和port。
连接成功后会通过代理回调
Timeout为负,表示不设置超时时间。这里的data就是 上一篇 中拼接的打印数据。
写入完成后回调
断开连接有以下几种方法
连接断开后回调
读取到数据会回调
网口打印机一般都支持状态查询,查询指令如下:
可以通过 上一篇 介绍指令拼接方法,查询打印机的状态。
本篇只是简单介绍了,通过蓝牙和Socket连接打印机的方法。虽然可以初步完成连接和打印,但是,在真正的项目中使用还是远远不够的。这里还有很多情况需要考虑,比如连接断开、打印机异常、打印机缓冲区满、打印机缺纸等。我们可以针对自身的业务情况,进行相应的处理。
Core Bluetooth Programming Guide
Getting the pixel data from a CGImage object
Core Bluetooth Programming Guide
iOS 蓝牙开发(一)
iOS 蓝牙开发(二)
iOS 蓝牙开发(三)
iOS 蓝牙开发(四)
在iOS中蓝牙相关实现都是在CoreBluetooth这个framework中的,所以我们创建一个单例类中需要先导入 #import CoreBluetooth/CoreBluetooth.h ,再后即可使用这个单例类进行管理我们蓝牙的扫描、连接、状态等实现。
当 central.state 为CBManagerStatePoweredOn即可开始扫描, 具体方法 [self.centralManager scanForPeripheralsWithServices:nil options:nil] 当调用 scanForPeripheralsWithServices:options: 函数时就会实时调用其代理方法 - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
peripheral 是外设类 advertisementData 是广播的值,一般携带设备名, serviceUUID 等信息。 RSSI 绝对值越大,表示信号越差,设备离的越远。如果想装换成百分比强度, (RSSI+100)/1001 (这是一个约数,蓝牙信号值并不一定是-100 - 0的值)
蓝牙的连接是当中心设备扫描到可用外设后, 利用函数 [self.centralManager connectPeripheral:peripheral options:nil]; 进行链接, 当函数被调用后, 就会回调其对应的代理函数。
本篇笔记主要是记录如何初始化蓝牙的 CBCentralManager 的中心管理类,并记录如何实现扫描周边外设、如何链接、获取蓝牙当前状态。
iOS蓝牙开发相关知识点和注意事项
总结一下蓝牙开发相关的知识点和注意事项,做个笔记,也希望你们能少踩坑
(公司部分蓝牙项目为混编项目,蓝牙相关处理均采用了Objective-C,故本文????均采用OC,Swift处理相同)
蓝牙4.0包含两个蓝牙标准,它是一个是 双模 的标准,它包含 传统蓝牙部分(也称经典蓝牙) 和 低功耗蓝牙部分(BLE) , 二者适用于不同的应用场景和应用条件。他们的特点如下
所以蓝牙4.0是集成了传统蓝牙和低功耗蓝牙两个标准的,并不只是低功耗蓝牙
蓝牙4.0支持两种部署方式: 双模式 和 单模式 ,双模同时支持经典蓝牙和低功耗蓝牙,而单模则只支持其中一种。
二者更多细节详见: 传统蓝牙和低功耗蓝牙的区别
iOS中蓝牙相关功能都封装进了 CoreBluetooth 类中,其中有几个常见的参数和概念
具体API参考 CoreBluetooth蓝牙开发
保存到数组中的设备可通过 UUID 来进行区分。从 iOS7之后苹果不提供外设的mac地址,外设的唯一标识换成了由mac封装加密后的UUID,需要注意的是不同的手机获取同一个外设的UUID是不同的,所以在不同手机之间UUID不是唯一的,但在本机上可以作为唯一标识(特殊情况手机刷机后也会改变UUID)。
如何获取Mac地址
一般使用场景是根据Mac地址区分某个外设
注意点:
写入数据时可能会遇到需要分包发送的情况,我们可以通过下面的API或许当前特征支持的最大的单条写入长度
maxLength 一般取决于蓝牙模块内部接收 缓冲区 的大小,很多硬件设备这个缓冲区的大小是 20 字节, 这个大小也和特征的写入权限有关,像具有写入权限 withResponse 类的特征其大小一般为 512 字节,当然这些都是取决于设备测的设置;
当我们单次发送的数据字节长度大于 maxLength 时,我们就需要采用分包的方式来发送数据了,
分包发送的逻辑类似于下面
这边延时主要是设备侧的接收模块接收数据以及处理能力有限
外围设备测和中心设备(大部分情况下是手机)保持蓝牙连接的状态下,如果长时间不产生交互,蓝牙就会断开,所以为了保持两者持续的连接状态,需要做保活处理,也就是需要持续的发送心跳包(watchdog)。相应的处理是使用一个定时器定时向设备侧发送符合设备协议格式的心跳包。
断开连接很简单,只需要调用 [self.centralManager cancelPeripheralConnection:peripheral] 传入需要断开连接的设备对象就行了。断开连接时会自动调用 centralManager:didDisconnectPeripheral:error: 代理方法。
按照之前的惯例,当error为nil时表示断开成功,error不为nil时断开失败。这种理解是错误的。
当你调用 cancelPeripheralConnection: 方法(主动断开)断开连接时error为nil ; 没有调用这个方法(异常断开)而断开时error返回的是异常断开的原因。也可以理解为主动调用断开连接方法一定会断开
接下来就是断开重连的问题了,对蓝牙功能进行封装时肯定少不了断开重连。首先断开时可通过上面的代理方法的error是否为nil判断是否是异常断开,一般情况下异常断开时是需要重连的
原因就是当设备断开连接后 peripheral.services 为nil了,当然 service.characteristics 也是nil,所以需要在断开连接时把保存这个设备对应的服务和特征全部清除,然后在连接成功时重新过一遍发现服务和发现特征的流程就好了。
iOS7 开始,Apple加入了Beacon围栏检测的API, ( iBeacon-维基百科 ), 其工作方式是,配备有低功耗蓝牙(BLE)通信功能的设备使用 BLE 技术向周围发送自己特有的 ID,接收到该 ID 的应用软件会根据该 ID 采取一些行动。比如,在店铺里设置 iBeacon 通信模块的话,便可让 iPhone 和 iPad 上运行一资讯告知服务器,或者由服务器向顾客发送折扣券及进店积分, 或者公司的手机打卡,只要手机靠近打卡器一定范围,手机测就向打开器发送打卡信息,从而自动打卡。这种场景还有很多。 其中一个最重要的功能就是App的唤醒功能(杀死后也能唤醒)
举一个我们的例子,我们的产品业务场景就是在进入车辆以后,需要使用蓝牙连接我们的后装车载设备以采集车辆信息和驾驶行为行程等,这里有一个问题就是在App被杀死的情况下如何唤醒App, 因为不可能要求用户每次都主动去打开App,这样体验太差。我们的做法是通过iBeacon,当我们的车辆点火以后,设备测通电,发出 iBeacon广播 ,App实现监听iBeacon相关功能后就可以唤醒我们App,然后在相应的回调的处理一些事情,比如通过蓝牙连接设备。这里的前提条件是我们的硬件设备测包含iBeacon模块,具有iBeacon功能,而且对iBeacon的广播频率也有一定的要求,长了可能唤醒的功能会不稳定,官方建议的好像是100ms,频率超高越耗电,但可以让手机或其它监听设备越快地发现iBeacon。标准的BLE广播距离是100m,这使Beacon在室内位置跟踪场景下的效果更理想。
关于iBeacon更多的使用及介绍请参考
苹果核 - iOS端近场围栏检测(一) ——iBeacon
iBeacon技术初探
iOS 蓝牙开发(三)
iOS 蓝牙开发(一)
iOS 蓝牙开发(二)
iOS 蓝牙开发(四)
前面记录了蓝牙如何进行扫描、链接、以及获取外设的服务和特征,本篇笔记我将记录如何实现 与外设做数据交互(explore and interact) 。
构建方法流程:链接成功-获取指定的服务与特征-订阅指定的特征值-通过具有写权限的特征值来写数据-最后在函数 didUpdateValueForCharacteristic 中获取蓝牙的反馈信息;
总结:
本篇笔记大概就是在接收到服务和特征后对数据进行写入的操作的过程,笔记中的重点在于要熟悉构建特征和服务的方法流程。熟悉流程,我们就能清楚知道当在写入数据时,系统蓝牙会在函数 didUpdateValueForCharacteristic 方法中给我们反馈写入是否成功的反馈信息。
分享标题:ios开发蓝牙,ios开发蓝牙类应用上架要求
本文URL:http://lswzjz.com/article/dseopgd.html