前言
上一篇主要介紹了部分ESC/POS指令集浮梢,包括一些常用的排版指令抹腿,打印位圖指令等。另外松却,還介紹了將圖片轉(zhuǎn)換成點陣圖的方法暴浦。在這篇文章中溅话,將主要介紹通過藍牙和Socket連接打印機,發(fā)送打印指令相關(guān)知識歌焦。這里將用到CoreBluetooth.framework
和CocoaAsyncSocket
飞几。
藍牙鏈接小票打印機
簡介
藍牙是一種支持設(shè)備間短距離通訊的無線電技術(shù)。iOS系統(tǒng)中独撇,有四個框架支持藍牙鏈接:
-
GameKit.framework
: 只能用于iOS設(shè)備之間的連接屑墨,多用于藍牙對戰(zhàn)的游戲,iOS7開始已過期纷铣; -
MultipeerConnectivity.framework
:只能用于iOS設(shè)備之間的連接卵史,從iOS7開始引入,主要用于替代GameKit
搜立; -
ExternalAccessory.framework
:可用于第三方藍牙設(shè)備交互以躯,但是藍牙設(shè)備必須經(jīng)過蘋果MFi認證; -
CoreBluetooth.framework
:目前最iOS平臺最流行的框架啄踊,并且設(shè)備不需要MFi認證忧设,手機至少4S以上,第三方設(shè)備必須支持藍牙4.0颠通;這里介紹的鏈接打印機就是使用此框架见转,因此開始前要確保打印機是支持藍牙4.0的;
CoreBluetooth框架有兩個核心概念蒜哀,central(中心)和 peripheral(外設(shè))斩箫,它們分別有自己對應(yīng)的API;這里顯然是手機作為central撵儿,藍牙打印機作為peripheral乘客;
步驟
1.初始化中心設(shè)備管理
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
2. 確認藍牙狀態(tài)
設(shè)置代理后,會回調(diào)此方法淀歇,確認藍牙狀態(tài)易核,當(dāng)狀態(tài)為CBCentralManagerStatePoweredOn
才能去掃描設(shè)備,藍牙狀態(tài)變化時浪默,也會回調(diào)此方法
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
NSString * state = nil;
switch ([central state])
{
case CBCentralManagerStateUnsupported:
state = @"The platform/hardware doesn't support Bluetooth Low Energy.";
break;
case CBCentralManagerStateUnauthorized:
state = @"The app is not authorized to use Bluetooth Low Energy.";
break;
case CBCentralManagerStatePoweredOff:
state = @"Bluetooth is currently powered off.";
break;
case CBCentralManagerStatePoweredOn:
state = @"work";
break;
case CBCentralManagerStateUnknown:
default:
;
}
NSLog(@"Central manager state: %@", state);
}
3. 掃描外設(shè)
調(diào)用此方法開始掃描外設(shè)
注意:第一個參數(shù)指定一個CBUUID
對象數(shù)組牡直,每個對象表示外圍設(shè)備正在通告的服務(wù)的通用唯一標(biāo)識符(UUID)。此時纳决,僅返回公布這些服務(wù)的外設(shè)碰逸。當(dāng)參數(shù)為nil
,則返回所有已發(fā)現(xiàn)的外設(shè)阔加,而不管其支持的服務(wù)是什么饵史。
[self.centralManager scanForPeripheralsWithServices:nil options:nil];
當(dāng)掃描到4.0外設(shè)后會回調(diào)此方法,這里包含設(shè)備的相關(guān)信息,如名稱胳喷、UUID湃番、信號強度等;
/*
掃描吭露,發(fā)現(xiàn)設(shè)備后會調(diào)用
*/
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
NSString *str = [NSString stringWithFormat:@"----------------發(fā)現(xiàn)藍牙外設(shè): peripheral: %@ rssi: %@, UUID: advertisementData: %@ ", peripheral, RSSI, advertisementData];
NSLog(@"%@",str);
if (![self.peripherals containsObject:peripheral]) {
[self.peripherals addObject:peripheral];
}
}
4. 選擇外設(shè)進行連接
調(diào)用此方法連接外設(shè)
[self.centralManager connectPeripheral:peripheral options:nil];
注意:第一個參數(shù)是要連接的外設(shè)吠撮。第二個參數(shù)options
是可選的NSDictionary
,系統(tǒng)定義了一下三個鍵,它們的值都是NSNumber (Boolean)讲竿;默認為NO纬向。當(dāng)設(shè)置為YES,則應(yīng)用進入后臺或者被掛起后戴卜,系統(tǒng)會用Alert通知藍牙外設(shè)的狀態(tài)變化逾条,效果是這樣
CBConnectPeripheralOptionNotifyOnConnectionKey;連接時Alert顯示
CBConnectPeripheralOptionNotifyOnDisconnectionKey;斷開時Alert顯示
CBConnectPeripheralOptionNotifyOnNotificationKey;接收到外設(shè)通知時Alert顯示
[self.centralManager connectPeripheral:peripheral options:@{
CBConnectPeripheralOptionNotifyOnConnectionKey : @YES,
CBConnectPeripheralOptionNotifyOnDisconnectionKey : @YES,
CBConnectPeripheralOptionNotifyOnNotificationKey : @YES
}];
連接成功或失敗,都有對應(yīng)的回調(diào)方法
/*
連接失敗后回調(diào)
*/
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
NSLog(@"%@",error);
}
/*
連接成功后回調(diào)
*/
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
peripheral.delegate = self;//設(shè)置代理
[central stopScan];//停止掃描外設(shè)
[peripheral discoverServices:nil];//尋找外設(shè)內(nèi)所包含的服務(wù)
}
5. 掃描外設(shè)中的服務(wù)和特征
連接成功后設(shè)置代理peripheral.delegate = self
,調(diào)用[peripheral discoverServices:nil];
尋找外設(shè)內(nèi)的服務(wù)投剥。這里的參數(shù)是一個存放CBUUID
對象的數(shù)組师脂,用于發(fā)現(xiàn)特定的服務(wù)。當(dāng)傳nil時江锨,表示發(fā)現(xiàn)外設(shè)內(nèi)所有的服務(wù)吃警。發(fā)現(xiàn)服務(wù)后系統(tǒng)會回調(diào)下面的方法:
/*
掃描到服務(wù)后回調(diào)
*/
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
if (error)
{
NSLog(@"Discovered services for %@ with error: %@", peripheral.name, [error localizedDescription]);
return;
}
for (CBService* service in peripheral.services) {
NSLog(@"掃描到的serviceUUID:%@",service.UUID);
//掃描特征
[peripheral discoverCharacteristics:nil forService:service];
}
}
發(fā)現(xiàn)服務(wù)后,調(diào)用[peripheral discoverCharacteristics:nil forService:service];
去發(fā)現(xiàn)服務(wù)中包含的特征啄育。和上面幾個方法一樣酌心,第一個參數(shù)用于發(fā)現(xiàn)指定的特征。為nil時挑豌,表示發(fā)現(xiàn)服務(wù)的所有特征安券。
/*
掃描到特性后回調(diào)
*/
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
if (error)
{
NSLog(@"Discovered characteristics for %@ with error: %@", service.UUID, [error localizedDescription]);
return;
}
for (CBCharacteristic * cha in service.characteristics)
{
CBCharacteristicProperties p = cha.properties;
if (p & CBCharacteristicPropertyBroadcast) {//廣播特征
}
if (p & CBCharacteristicPropertyRead) {//讀取特征
self.characteristicRead = cha;
}
if (p & CBCharacteristicPropertyWriteWithoutResponse) {//無反饋寫入特征
}
if (p & CBCharacteristicPropertyWrite) {//有反饋寫入特征
self.peripheral = peripheral;
self.characteristicInfo = cha;
}
if (p & CBCharacteristicPropertyNotify) {//通知特征
self.characteristicNotify = cha;
[self.peripheral setNotifyValue:YES forCharacteristic:self.characteristicNotify];
NSLog(@"characteristic uuid:%@ value:%@",cha.UUID,cha.value);
}
}
}
當(dāng)掃描到寫入特征時,保存氓英,用于寫入數(shù)據(jù)侯勉。
6. 寫入數(shù)據(jù)
寫入數(shù)據(jù),我們只需要調(diào)用方法
[self.peripheral writeValue:subData forCharacteristic:self.characteristicInfo type:CBCharacteristicWriteWithResponse];
這里的self.peripheral
就是連接的外設(shè)铝阐,self.characteristicInfo
就是之前保存的寫入特征址貌;這里最好使用CBCharacteristicPropertyWrite
特征,并且type
選擇CBCharacteristicWriteWithResponse
徘键。當(dāng)寫入數(shù)據(jù)成功后练对,系統(tǒng)會通過下面這個方法通知我們:
-(void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
if (error) {
NSLog(@"====error%@",error);
}else{
NSLog(@"====寫入成功 %@", characteristic);
}
}
由于藍牙設(shè)備每次可寫入的數(shù)據(jù)量是有限制的,因此吹害,我們需要將之前拼接的打印數(shù)據(jù)進行拆分螟凭,分批發(fā)送給打印機
- (void)printLongData:(NSData *)printContent{
NSUInteger cellMin;
NSUInteger cellLen;
//數(shù)據(jù)長度
NSUInteger strLength = [printContent length];
if (strLength < 1) {
return;
}
//MAX_CHARACTERISTIC_VALUE_SIZE = 120
NSUInteger cellCount = (strLength % MAX_CHARACTERISTIC_VALUE_SIZE) ? (strLength/MAX_CHARACTERISTIC_VALUE_SIZE + 1):(strLength/MAX_CHARACTERISTIC_VALUE_SIZE);
for (int i = 0; i < cellCount; i++) {
cellMin = i*MAX_CHARACTERISTIC_VALUE_SIZE;
if (cellMin + MAX_CHARACTERISTIC_VALUE_SIZE > strLength) {
cellLen = strLength-cellMin;
}
else {
cellLen = MAX_CHARACTERISTIC_VALUE_SIZE;
}
NSRange rang = NSMakeRange(cellMin, cellLen);
// 截取打印數(shù)據(jù)
NSData *subData = [printContent subdataWithRange:rang];
//循環(huán)寫入數(shù)據(jù)
[self.peripheral writeValue:subData forCharacteristic:self.characteristicInfo type:CBCharacteristicWriteWithResponse];
}
}
這里的MAX_CHARACTERISTIC_VALUE_SIZE
是個宏定義,表示每次發(fā)送的數(shù)據(jù)長度赠制,經(jīng)筆者測試赂摆,當(dāng)MAX_CHARACTERISTIC_VALUE_SIZE = 20
時挟憔,打印文字是正常速度钟些。但打印圖片的速度非常慢烟号,應(yīng)該在硬件允許的范圍內(nèi),每次發(fā)盡量多的數(shù)據(jù)政恍。不同品牌型號的打印機汪拥,這個參數(shù)是不同的,筆者的藍牙打印機該值最多到140篙耗。超出后會出現(xiàn)無法打印問題迫筑。最后筆者將該值定為MAX_CHARACTERISTIC_VALUE_SIZE = 120
,測試了公司幾臺打印機都沒有問題宗弯。
另外iOS9以后增加了方法maximumWriteValueLengthForType:
可以獲取寫入特診的最大寫入數(shù)據(jù)量脯燃,但經(jīng)筆者測試,對于部分打印機(比如我們公司的)是不準(zhǔn)確的蒙保,因此辕棚,不要太依賴此方法,最好還是自己取一個合適的值邓厕。
注意:每個打印機都有一個緩沖區(qū)逝嚎,緩沖區(qū)的大小視品牌型號有所不同。打印機的打印速度有限详恼,如果我們瞬間發(fā)送大量的數(shù)據(jù)給打印機补君,會造成打印機緩沖區(qū)滿。緩沖區(qū)滿后昧互,如繼續(xù)寫入挽铁,可能會出現(xiàn)數(shù)據(jù)丟失,打印亂碼敞掘。
Socket鏈接小票打印機
簡介
這里使用CocoaAsyncSocket開源框架屿储,與打印機進行Socket
連接。CocoaAsyncSocket
中主要包含兩個類:
-
GCDAsyncSocket
:用GCD搭建的基于TCP/IP協(xié)議的socket網(wǎng)絡(luò)庫; -
GCDAsyncUdpSocket
:用GCD搭建的基于UDP/IP協(xié)議的socket網(wǎng)絡(luò)庫渐逃。
這里我們只用到GCDAsyncSocket
够掠,因此只需要將GCDAsyncSocket.h
和GCDAsyncSocket.m
兩個文件導(dǎo)入項目。
注意:手機和打印機必須在同一局域網(wǎng)下茄菊,設(shè)置到打印機的host和port疯潭。
步驟
1、遵循GCDAsyncSocketDelegate
協(xié)議
@interface MNSocketManager()<GCDAsyncSocketDelegate>
2面殖、聲明屬性
@property (nonatomic, strong) GCDAsyncSocket *asyncSocket;
3竖哩、初始化GCDAsyncSocket
對象
self.asyncSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
4、連接打印機
NSError *error = nil;
[self.asyncSocket connectToHost:host onPort:port withTimeout:timeout error:&error];
連接成功后會通過代理回調(diào)
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port {
}
5脊僚、發(fā)送數(shù)據(jù)給打印機
Timeout為負相叁,表示不設(shè)置超時時間遵绰。這里的data就是上一篇中拼接的打印數(shù)據(jù)。
[self.asyncSocket writeData:data withTimeout:-1 tag:0];
寫入完成后回調(diào)
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag {
NSLog(@"寫入完成");
}
6增淹、斷開連接
斷開連接有以下幾種方法
[self.asyncSocket disconnect];
[self.asyncSocket disconnectAfterReading];
[self.asyncSocket disconnectAfterWriting];
[self.asyncSocket disconnectAfterReadingAndWriting];
連接斷開后回調(diào)
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err {
NSLog(@"連接斷開");
}
7椿访、讀取數(shù)據(jù)
讀取到數(shù)據(jù)會回調(diào)
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
NSLog(@"讀取完成");
}
網(wǎng)口打印機一般都支持狀態(tài)查詢,查詢指令如下:
可以通過上一篇介紹指令拼接方法虑润,查詢打印機的狀態(tài)成玫。
總結(jié)
本篇只是簡單介紹了,通過藍牙和Socket連接打印機的方法拳喻。雖然可以初步完成連接和打印哭当,但是,在真正的項目中使用還是遠遠不夠的冗澈。這里還有很多情況需要考慮钦勘,比如連接斷開、打印機異常亚亲、打印機緩沖區(qū)滿彻采、打印機缺紙等。我們可以針對自身的業(yè)務(wù)情況朵栖,進行相應(yīng)的處理颊亮。
參考
Core Bluetooth Programming Guide