iOS開發(fā)之藍牙/Socket鏈接小票打印機(二)

前言

上一篇主要介紹了部分ESC/POS指令集浮梢,包括一些常用的排版指令抹腿,打印位圖指令等。另外松却,還介紹了將圖片轉(zhuǎn)換成點陣圖的方法暴浦。在這篇文章中溅话,將主要介紹通過藍牙和Socket連接打印機,發(fā)送打印指令相關(guān)知識歌焦。這里將用到CoreBluetooth.frameworkCocoaAsyncSocket飞几。

藍牙鏈接小票打印機

簡介

藍牙是一種支持設(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.hGCDAsyncSocket.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)查詢指令

可以通過上一篇介紹指令拼接方法虑润,查詢打印機的狀態(tài)成玫。

總結(jié)

本篇只是簡單介紹了,通過藍牙和Socket連接打印機的方法拳喻。雖然可以初步完成連接和打印哭当,但是,在真正的項目中使用還是遠遠不夠的冗澈。這里還有很多情況需要考慮钦勘,比如連接斷開、打印機異常亚亲、打印機緩沖區(qū)滿彻采、打印機缺紙等。我們可以針對自身的業(yè)務(wù)情況朵栖,進行相應(yīng)的處理颊亮。

參考

Core Bluetooth Programming Guide

Getting the pixel data from a CGImage object

Core Bluetooth Programming Guide

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市陨溅,隨后出現(xiàn)的幾起案子仅炊,更是在濱河造成了極大的恐慌雷恃,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異潭陪,居然都是意外死亡颠放,警方通過查閱死者的電腦和手機伪朽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門帮毁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人吉拳,你說我怎么就攤上這事质帅。” “怎么了留攒?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵煤惩,是天一觀的道長。 經(jīng)常有香客問我炼邀,道長魄揉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任拭宁,我火速辦了婚禮洛退,結(jié)果婚禮上瓣俯,老公的妹妹穿的比我還像新娘。我一直安慰自己兵怯,他們只是感情好彩匕,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著摇零,像睡著了一般推掸。 火紅的嫁衣襯著肌膚如雪桶蝎。 梳的紋絲不亂的頭發(fā)上驻仅,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機與錄音登渣,去河邊找鬼噪服。 笑死,一個胖子當(dāng)著我的面吹牛胜茧,可吹牛的內(nèi)容都是我干的粘优。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼呻顽,長吁一口氣:“原來是場噩夢啊……” “哼雹顺!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起廊遍,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤嬉愧,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后喉前,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體没酣,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年卵迂,在試婚紗的時候發(fā)現(xiàn)自己被綠了裕便。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡见咒,死狀恐怖偿衰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情改览,我是刑警寧澤下翎,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站恃疯,受9級特大地震影響漏设,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜今妄,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一郑口、第九天 我趴在偏房一處隱蔽的房頂上張望鸳碧。 院中可真熱鬧,春花似錦犬性、人聲如沸瞻离。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽套利。三九已至,卻和暖如春鹤耍,著一層夾襖步出監(jiān)牢的瞬間肉迫,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工稿黄, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留喊衫,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓杆怕,卻偏偏與公主長得像族购,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子陵珍,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 藍牙簡介 藍牙( Bluetooth? ):是一種無線技術(shù)標(biāo)準(zhǔn)寝杖,可實現(xiàn)固定設(shè)備、移動設(shè)備和樓宇個人域網(wǎng)之間的短距離...
    Chefil閱讀 2,041評論 2 19
  • 原文:http://www.myexception.cn/operating-system/2052286.htm...
    KYM1988閱讀 1,944評論 2 2
  • 在寫這個博客之前互纯,空余時間抽看了近一個月的文檔和Demo瑟幕,系統(tǒng)給的解釋很詳細,接口也比較實用伟姐,唯獨有一點收苏,對于設(shè)備...
    木易林1閱讀 3,353評論 3 4
  • 最近競品公司出了一個接入藍牙打印機的功能,作為競爭對手公司肯定不能少所以就給我分了任務(wù)愤兵,搞定藍牙打印機 首先介紹一...
    呆北默閱讀 3,267評論 12 10
  • 像燈光一樣 總有一天我會完成我的夢想 眼神不要那么兇 這樣不美 要 散發(fā)
    治愈的卡其色亮亮蝦閱讀 170評論 0 0