最近競品公司出了一個接入藍牙打印機的功能,作為競爭對手公司肯定不能少所以就給我分了任務,搞定藍牙打印機
首先介紹一個公司的藍牙打印功能設想,因為公司已經具備了wife打印機的打印功能而且非常完善,于是就在想是不是可以吧藍牙當一個wife去使用這樣工作量能少N倍啊,想到這里那就很簡單了找資料開始搞
一.首先你得先能和你玩具-藍牙打印機連接上并且確定藍牙打印機并且能給藍牙打印機透傳數據,這里科普一下藍牙協議這個玩意藍牙協議分為藍牙2.0和藍牙4.0;
首先你得清楚一點支持藍牙2.0協議的設備和藍牙4.0的區別,那就是藍牙2.0的設備只能手動去建立藍牙連接 而4.0設備卻可以被簡單的程序掃描到并且建立連接,為什么2.0設備沒有被拋棄呢,這樣能確保你的藍牙設備非常安全的比如蘋果的很多藍牙設備都是2.0的
1.藍牙2.0
iOS開發中藍牙2.0協議使用ExternalAccessory這個基礎庫 由于2.0協議的特殊所以你使用遵循2.0協議的藍牙設備的時候需要得知該設備藍牙協議名稱
只有在這里加了這個藍牙協議名稱你才能找到你的設備
具體視線代碼如下,不建議直接復制 因為個人寫的有殘缺 ?但是主要的功能都已經實現
.h中代碼如下
#import#import@interface ExternalAccessoryManager : NSObject
//會話渠道
@property (nonatomic,strong) EASession *session;
//代表打印機
@property (nonatomic,strong) EAAccessory *accessory;
//藍牙設備列
@property (nonatomic,strong) NSMutableArray *deviceArr;
/**
藍牙2.0協議管理單例
@return self
*/
+ (instancetype)shareExternalAccessoryManager;
//得到比索隆2.0藍牙外設
- (NSArray *)getConnectBuleToolthDevice;
//打印
- (void)printMsgData:(NSData *)data;
@end
.m文件代碼如下
#import "ExternalAccessoryManager.h"static ExternalAccessoryManager * g_ExternalAccessoryManager = nil;@interface ExternalAccessoryManager (){
}
@property (nonatomic,strong) NSData *printData;
@end
@implementation ExternalAccessoryManager
+ (instancetype)shareExternalAccessoryManager{
static dispatch_once_t oneceToken;
dispatch_once(&oneceToken, ^{
g_ExternalAccessoryManager = [[self alloc] init];
});
return g_ExternalAccessoryManager;
}
- (id)init{
self = [super init];
if (self) {
self.deviceArr = [NSMutableArray array];
}
return self;
}
- (NSArray *)getConnectBuleToolthDevice{
EAAccessoryManager *accessoryManager = [EAAccessoryManager sharedAccessoryManager];
NSArray *connectedAccessories = [accessoryManager connectedAccessories];
for (EAAccessory *accessory in connectedAccessories) {
//找到比索隆打印機
if ([@"com.bixolon.protocol" isEqualToString:[accessory.protocolStrings firstObject]] == YES) {
[self.deviceArr addObject:accessory];
}
}
return connectedAccessories;
}
//打印
- (void)printMsgData:(NSData *)data{
// search our device
for (EAAccessory *accessory in self.deviceArr) {
if ([@"com.bixolon.protocol" isEqualToString:[accessory.protocolStrings firstObject]] == YES) {
//? ? ? ? ? ? NSMutableString *info = [[NSMutableString alloc] init];
//? ? ? ? ? ? // 硬件的協議字符串和硬件廠商提供的一致,這個就是我們要找的設備了!
//? ? ? ? ? ? // log:可以打印一下該硬件的相關資訊
//? ? ? ? ? ? for (NSString *proStr in accessory.protocolStrings) {
//? ? ? ? ? ? ? ? [info appendFormat:@"protocolString = %@\n", proStr];
//? ? ? ? ? ? ? ? NSLog(@"打印機協議%@",proStr);
//? ? ? ? ? ? }
//? ? ? ? ? ? [info appendFormat:@"\n"];
//? ? ? ? ? ? [info appendFormat:@"manufacturer = %@\n", accessory.manufacturer];
//? ? ? ? ? ? [info appendFormat:@"name = %@\n", accessory.name];
//? ? ? ? ? ? [info appendFormat:@"modelNumber = %@\n", accessory.modelNumber];
//? ? ? ? ? ? [info appendFormat:@"serialNumber = %@\n", accessory.serialNumber];
//? ? ? ? ? ? [info appendFormat:@"firmwareRevision = %@\n", accessory.firmwareRevision];
//? ? ? ? ? ? [info appendFormat:@"hardwareRevision = %@\n", accessory.hardwareRevision];
//有打印設備并且有打印內容
if (accessory && data.length > 0) {
self.printData = data;
self.accessory = accessory;
[self openSession];
}
}
}
}
//打開傳輸通道
- (BOOL)openSession {
// 根據已經連接的EAAccessory對象和這個協議(反向域名字符串)來創建EASession對象,并打開輸入、輸出通道
self.session = [[EASession alloc] initWithAccessory:self.accessory forProtocol:@"com.bixolon.protocol"];
if(self.session != nil) {
// open input stream? 接收
self.session.inputStream.delegate = self;
[self.session.inputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[self.session.inputStream open];
// open output stream 傳輸
self.session.outputStream.delegate = self;
[self.session.outputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[self.session.outputStream open];
}
else {
NSLog(@"2.0設備未能創建會話");
}
return (nil != self.session);
}
//NSStreamDelegate
// delegate回調的方法
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
switch (eventCode) {
//沒有事件
case NSStreamEventNone:
break;
//事件打開完成
case NSStreamEventOpenCompleted:
break;
//有數據傳過來
case NSStreamEventHasBytesAvailable:{
//NSLog(@"Input stream is ready");
// 接收到硬件數據了,根據指令定義對數據進行解析。
}
break;
//有可用控件(發送內容)
case NSStreamEventHasSpaceAvailable:{
//NSLog(@"Output stream is ready");
// 可以發送數據給硬件了
[self writeToDevice];
}
break;
//事件發生錯誤
case NSStreamEventErrorOccurred:{
}
break;
//事件結束后
case NSStreamEventEndEncountered:{
//寫入數據成功后關閉通道
[self.session.outputStream close];
}
break;
default:
break;
}
}
- (void)writeToDevice{
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"print_guide_airkiss"ofType:@"txt"];
self.printData = [NSData dataWithContentsOfFile:filePath];
NSInteger length = [self.printData length];
NSString *dateStr = [[NSString alloc] initWithData:self.printData encoding:NSUTF8StringEncoding];
NSLog(@"內容編碼%@",dateStr);
if (length > 0) {
[self.session.outputStream write:[self.printData bytes] maxLength:length];
}
}
2.藍牙4.0就更簡單了? 它使用的是CoreBluetooth我在網上找到了一個大神封裝的關于打印方面的一個第三方使用很是方便 大家可以去看看這里附上大神demo的readeME ?這里可以很方便快捷的找到大神的數據
# HLBluetoothDemo# 提醒有部分開發人員,加群之后,直接問設備掃描不到,服務掃描出錯,特性掃描報錯,怎么辦?對于這類問題,要嚴厲批評,都是因為懶,沒有用心去看官方文檔或者關于CoreBluetooth的教程。
所以對于藍牙的連接處理流程不清晰的,嚴重建議先看[iOS CoreBluetooth 的使用講解](http://www.lxweimin.com/p/1f479b6ab6df)
自己動手寫Demo,把系統的CoreBluetooth的使用,以及藍牙的流程搞懂了,再開始做藍牙打印的功能。# 引言該項目中包含兩個部分的工具類`HLBluetooth` 和`HLPrinter`,藍牙操作和打印小票功能。
> 如果只是做藍牙打印機打印小票的功能,可以看我的另一個工程[SEBLEPrinter](https://github.com/Halley-Wong/SEBLEPrinter)因為系統的藍牙操作庫是用delegate實現的,步驟比較繁多,操作很零散,需要寫一堆的代理方法,特別麻煩
所以我用block方式重寫了,藍牙管理的所有代碼在HLBluetooth目錄中。
又因為項目中要用藍牙控制打印機打印小票,我又把藍牙打印機的操作封裝了一下,所有代碼在HLPrinter目錄下。
# HLBluetooth介紹用block改寫后,使用大致分為三步:* 獲取藍牙模塊的狀態* 掃描藍牙外設* 連接、掃描服務、掃描特性、掃描描述。因為連接、掃描服務、掃描特性、掃描描述也是屬于不同的階段,所以在block返回時,也有階段值返回。
~~---------------------------------------------------------------------------------------------------------~~
除了上面這些代理方法改寫的block API之外,還有一些操作性方法,比如:* 讀取特性值* 讀取描述值* 往特性中寫入數據* 往描述中寫入數據* 讀取信號數據* 取消藍牙連接...以上這些方法也提供block方式和一般的調用方式。
# HLPrinter介紹藍牙打印機模板可以打印的格式有* 單行文字格式```[printer appendText:title alignment:HLTextAlignmentCenter fontSize:HLFontSizeTitleBig];[printer appendText:str1 alignment:HLTextAlignmentCenter]; ```* 左標題右參數格式```[printer appendTitle:@"時間:" value:@"2016-04-27 10:01:50" valueOffset:150];[printer appendTitle:@"訂單:" value:@"4000020160427100150" valueOffset:150];[printer appendTitle:@"總計:" value:totalStr];[printer appendTitle:@"實收:" value:@"100.00"];```* 三列數據格式```[printer appendLeftText:@"商品" middleText:@"數量" rightText:@"單價" isTitle:YES];[printer appendLeftText:dict[@"name"] middleText:dict[@"amount"] rightText:dict[@"price"] isTitle:NO];```* 分隔線```[printer appendSeperatorLine];```* 圖片```[printer appendImage:[UIImage imageNamed:@"ico180"] alignment:HLTextAlignmentCenter maxWidth:300];```* 二維碼```[printer appendQRCodeWithInfo:@"www.baidu.com" size:10];[printer appendQRCodeWithInfo:@"www.baidu.com"];```* 條形碼```[printer appendBarCodeWithInfo:@"123456789012"];```# 效果圖 # 使用方式關于詳細的BLE使用方式和打印小票的功能,在[這里有篇文章詳細說明](http://www.lxweimin.com/p/90cc08d11b5a)打印機的指令有ASCII、10進制和16進制三種,我使用的是16進制。```? ? Byte QRSize [] = {0x1D,0x28,0x6B,0x03,0x00,0x31,0x43,size}; // 這是16進制,其中最后一個size是10進制數,轉換為NSData后,會被轉換為16進制。? ? Byte QRSize [] = {29,40,107,3,0,49,67,size}; // 這是10進制。```# 補充一些參數:>據佳博的一技術人員提供的一些參數:
漢字是24 x 24點陣,字符是12 x 24。
58mm 型打印機橫向寬度384個點。(可是我用文字設置相對位置測試確實368,囧)
80mm 型打印機橫向寬度576個點。
1mm 大概是8個點。
# 更新修復部分型號打印亂碼,亂碼后再次打印沒反應的Bug。(2016-06-13,佳博 Gp-58MBIII和GP58MBIII和芯燁 XPrinter某型號測試通過) demo中也有一個使用的例子
如有使用錯誤或者更好的建議,請issues我。關于藍牙打印機的問題,也歡迎大家加入群:552735579(iOS藍牙打印機開發)。
具體4.0代碼請大家自己去根據https://github.com/Halley-Wong/SEBLEPrinter 簡單清晰明了 可以直接拿來使用的代碼
3.以上兩步做完之后就建立了你的app和藍牙設備之間的連接,但是因為我是要打印所以那么還需要和服務器建立長連接實時接收服務器給的數據同時將數據透傳給藍牙打印機。關于打印機指令以及對應的什么模板 打印原理之類這些廠商都會給你提供文檔 ?之前沒有想到這樣解決打印之前我就是通過了解這些指令用一行行的代碼對其進行模板化 其中的辛酸苦辣簡直不是一句話能解釋通的 ?當然這只是一部分打印機 ?大多數打印機廠商提供的SDK還是很好用的 ?所以考驗你們老板對你是不是真心的時候到了?
這里我是使用MQTT協議對服務器進行連接的,這個連接將有助與你詳細了解MQTT
https://mcxiaoke.gitbooks.io/mqtt-cn/content/mqtt/01-Introduction.html
關于MQTT在iOS中的具體使用見我的另一片文章在這里就不多說了 ?
所以總結一下我的app就是找到打印機并建立與打印機之間的聯系 ?同時與服務器建立長連接 ?那么這時候你就可以理解我的app和藍牙設備加起來這才是一個完整的打印機 ?然后這個打印機與服務器連接起來了 就可以接受服務器發送的打印指令了
好了文筆不好就寫到這吧