最近公司给商户做的App 允许App把卖出的商品信息通过打印机 打印标签
所以了解了一下iOS 和 打印机 之间的交互 (Ps:用的不是UIPrinter 那个扫面打印机 发送信息打印的那个框架)
主要功能 打印 .中文. 数字. 二维码
1.连接打印机
连接打印机可以通过 网线 USB 蓝牙 或者WiFi . 我们用App肯定是通过WiFi或者蓝牙连接
至于蓝牙怎么连接 可以 看看前面的文章,本文采用WiFi 链接.
App和打印机通过socket通信.使用的是 GCDAsyncSocket
做过socket通信的应该对这个库都贼鸡儿熟悉, 不多介绍.想了解的可以看看资料,用法也很简单Git地址
App通过socket向打印机发送指令控制打印机动作. 所谓指令就是标题ESC/POS 指令
开发之前先耐心看一下指令集,现在脑中有一个概念
通过端口和IP地址 连接打印机
@IBAction func connectAction(_ sender: Any) { let queue = DispatchQueue.global() self.socket = GCDAsyncSocket.init(delegate: self, delegateQueue: queue) do { try self.socket.connect(toHost: address, onPort: 9100) } catch { print("出毛病了") } }
可以实现GCDAsyncSocketDelegate 查看socket的连接状态
extension HomeViewController: GCDAsyncSocketDelegate { func socket(_ sock: GCDAsyncSocket, didConnectTo url: URL) { } func socket(_ sock: GCDAsyncSocket, didConnectToHost host: String, port: UInt16) { print("链接成功") } func socketDidDisconnect(_ sock: GCDAsyncSocket, withError err: Error?) { print("断开连接") } func socket(_ sock: GCDAsyncSocket, didWriteDataWithTag tag: Int) { print("写入成功") } func socket(_ sock: GCDAsyncSocket, shouldTimeoutReadWithTag tag: Int, elapsed: TimeInterval, bytesDone length: UInt) -> TimeInterval { return 10 } }
2.POS简单指令
/** * 这些数据源自爱普生指令集,为POS机硬件默认 */ let ESC:UInt8 = 27;//换码 let FS:UInt8 = 28;//文本分隔符 let GS:UInt8 = 29;//组分隔符 let DLE:UInt8 = 16;//数据连接换码 let EOT:UInt8 = 4;//传输结束 let ENQ:UInt8 = 5;//询问字符 let SP:UInt8 = 32;//空格 let HT:UInt8 = 9;//横向列表 let LF:UInt8 = 10;//打印并换行(水平定位) let CR:UInt8 = 13;//归位键 let FF:UInt8 = 12;//走纸控制(打印并回到标准模式(在页模式下) ) let CAN:UInt8 = 24;//作废(页模式下取消打印数据 )
swift方法
//MARK:-- ------------------------打印机初始化----------------------------- /** * 打印机初始化 * @return */ func init_printer() -> [UInt8] { let foo : [UInt8] = [UInt8(ESC), 64] return foo } //MARK:-- ------------------------换行----------------------------- /** * 换行 * @param * @return */ func nextLine(number:Int) -> [UInt8] { var foo:[UInt8] = [UInt8].init(); for _ in 0..<number { foo.append(LF) } return foo } //MARK:-- ------------------------打印空格------------------------- /** * 打印空格 * @param * @return */ func blankWithCount(number:Int) -> [UInt8] { var foo:[UInt8] = [UInt8].init(); for _ in 0..<number { foo.append(SP) } return foo } //MARK:-- ------------------------下划线----------------------------- /** * 绘制下划线(1点宽) * @return */ func underlineWithOneDotWidthOn() -> [UInt8] { var foo:[UInt8] = [UInt8].init(); foo.append(ESC); foo.append(45) foo.append(1) return foo }
/** * 绘制下划线(2点宽) * @return */ func underlineWithTwoDotWidthOn() -> [UInt8] { var foo:[UInt8] = [UInt8].init(); foo.append(ESC); foo.append(45) foo.append(2) return foo } /** * 取消绘制下划线 * @return */ func underlineOff() -> [UInt8] { var foo:[UInt8] = [UInt8].init(); foo.append(ESC); foo.append(45) foo.append(0) return foo } //MARK:-- ------------------------加粗----------------------------- /** * 选择加粗模式 * @return */ func boldOn() -> [UInt8] { var foo:[UInt8] = [UInt8].init(); foo.append(ESC); foo.append(69) foo.append(0xF) return foo } /** * 取消加粗模式 * @return */ func boldOff() -> [UInt8] { var foo:[UInt8] = [UInt8].init(); foo.append(ESC); foo.append(69) foo.append(0) return foo } //MARK:-- ------------------------对齐----------------------------- /** * 左对齐 * @return */ func alignLeft() -> [UInt8] { var foo:[UInt8] = [UInt8].init(); foo.append(ESC); foo.append(97) foo.append(0) return foo } /** * 居中对齐 * @return */ func alignCenter() -> [UInt8] { var foo:[UInt8] = [UInt8].init(); foo.append(ESC); foo.append(97) foo.append(1) return foo } /** * 右对齐 * @return */ func alignRight() -> [UInt8] { var foo:[UInt8] = [UInt8].init(); foo.append(ESC); foo.append(97) foo.append(2) return foo } /** * 水平方向向右移动col列 * @param col * @return */ func alignRight(col:UInt8) -> [UInt8] { var foo:[UInt8] = [UInt8].init(); foo.append(ESC); foo.append(68) foo.append(col) foo.append(0) return foo } //------------------------字体变大----------------------------- /** * 字体变大为标准的n倍 * @param num * @return */ func fontSize(font:Int8) -> [UInt8] { var realSize:UInt8 = 0 switch font { case 1: realSize = 0;break case 2: realSize = 17;break case 3: realSize = 34;break case 4: realSize = 51;break case 5: realSize = 68;break case 6: realSize = 85;break case 7: realSize = 102;break case 8: realSize = 119;break default: break } var foo:[UInt8] = [UInt8].init(); foo.append(GS); foo.append(33) foo.append(realSize) return foo } //------------------------字体变小----------------------------- /** * 字体取消倍宽倍高 * @param num * @return */ // public static byte[] fontSizeSetSmall(int num) //{ // byte[] result = new byte[3]; // result[0] = ESC; // result[1] = 33; // // return result; // } //------------------------切纸----------------------------- /** * 进纸并全部切割 * @return */ func feedPaperCutAll() -> [UInt8] { var foo:[UInt8] = [UInt8].init(); foo.append(GS); foo.append(86) foo.append(65) foo.append(0) return foo } /** * 进纸并切割(左边留一点不切) * @return */ func feedPaperCutPartial() -> [UInt8] { var foo:[UInt8] = [UInt8].init(); foo.append(GS); foo.append(86) foo.append(66) foo.append(0) return foo } func mergerPaper() -> [UInt8] { var foo:[UInt8] = [UInt8].init(); foo.append(ESC) foo.append(109) return foo }
上面是封装的一些常用的简单的方法, 大家需求各有不同,可以根据指令集 自己封装
3.编码格式
发送打印数据的时候 要注意编码格式
比如打印收据好多都是中文的, 打印机支持的是GBK标准. 发送的数据流就要按照这个编码格式编码
//MARK:-- 根据标签发送不同的指令 func sendPrinterContent(content:String) { let gbkEncoding = CFStringConvertEncodingToNSStringEncoding(UInt32(CFStringEncodings.GB_18030_2000.rawValue)) let data = content.data(using: String.Encoding(rawValue: gbkEncoding)) print("\(content)") self.socket.write(data!, withTimeout: -1, tag: 1); }
4.打印二维码
后台给的数据打印完毕之后,会在后边把一些信息以二维码的形式展示出来.
Emmmmmm 这个难住了. 但是资料或者说别人做好的东西,我们总是能搜到的.站在别人的肩膀上总是好的
这里借鉴了一下 大佬的Git地址
抄了两个方法 惭愧惭愧
首先分析一下打印机 打印图形的命令
2.3 图形打印指令图形打印指令见 表 2.36~表 2.39。
表 2.36
图形垂直取模数据填充
指令名称 |
图形垂直取模数据填充 |
指令代码 |
ASCII :ESC * m Hl Hh [d]k 十进制 :27 42 m Hl Hh [d]k 十六进制 :1B 2A m Hl Hh [d]k |
功能描述 |
打印纵向取模图像数据,参数意义如下:m 为点图格式: m 模式 水平比例 垂直比例0 8点单密度 ×2 ×3 Hl、Hh 为水平方向点数(Hl+256×Hh)[d]k 为点图数据 |
参数范围 |
58mm 纸宽: 1≤Hl + Hh×256≤384 80mm 纸宽: 1≤Hl + Hh×256≤576 |
默认值 |
无 |
注意事项 |
[d]k 相应位为 1 则表示该点打印,相应位为 0,则表示该点不打印图像水平方向超出打印区域的部分将被忽略点图数据与打印效果的关系如下: 此指令只填充打印缓存,图像的打印要在接收到打印指令后才开始,图像打印完毕后打印缓存被清空 |
33)点的图像分别打印填充图形数据后,可以继续填充其它信息,以使图形与其它信息一同被打印填充点图后,一般使用 ESC J(n = 24)指令进行打印,也可以使用 LF 指令进行打印,但是 LF 指令会引发进纸操作(按行间距进纸),使得多行图像间断不连续 |
|
使用示例 |
表 2.37
图片水平取模数据打印
指令名称 |
图片水平取模数据打印 |
指令代码 |
ASCII :GS v 0 十六进制:1D 76 30 m xL xH yL yH [d]k |
功能描述 |
打印横向取模图像数据,参数意义如下:m 为位图方式: m 模式水平比例垂直比例0,48 正常 ×1 ×11,49 倍宽 ×2 ×12,50 倍高 ×1 ×23,51 倍宽倍高 ×2 ×2 xL、xH 为水平方向字节数(xL + xH × 256)yL、yH 为竖直方向点数(yL + yH × 256)[d]k 为点图数据 |
参数范围 |
58mm 纸宽: 80mm 纸宽: |
默认值 |
无 |
注意事项 |
[d]k 相应位为 1 则表示该点打印,相应位为 0,则表示该点不打印若图像水平字节数超出打印区域,超出部分将被忽略此指令执行时按图像大小进纸,不受 ESC 2、ESC 3 的行间距设置影响此指令执行后,打印坐标复位到左边距位置处,图像内容被清空位图数据与打印效果的关系如下: |
续上表
此指令带有打印功能,边传数据边打印,不需要再使用打印指令
|
|
上面的文档 是两种取模打印方式. 图形水平取模打印 和 图形垂直取模打印
主要要细细的看 打印命令 和 注意事项
打印命令就是我们怎么发送个打印机的数据格式. 一定要按照这个格式发送才可以
注意事项解释一下就是: 我们拿到要转换的图片, 去图片位图bitmap. bitmap每一个点都要转换非黑即白,
打印机接受的数据只打印1 , 0不打印. 形成黑白的图片.
所以说转换的重点就是 1.拿到bitmap 2.转换成我们要打印的data
1.拿到bitmap
- (CGContextRef) newBitmapRGBA8ContextFromImage:(CGImageRef) image { CGContextRef context = NULL; CGColorSpaceRef colorSpace; uint32_t *bitmapData; size_t bitsPerPixel = 32; size_t bitsPerComponent = 8; size_t bytesPerPixel = bitsPerPixel / bitsPerComponent; size_t width = CGImageGetWidth(image); size_t height = CGImageGetHeight(image); size_t bytesPerRow = width * bytesPerPixel; size_t bufferLength = bytesPerRow * height; colorSpace = CGColorSpaceCreateDeviceRGB(); if(!colorSpace) { NSLog(@"Error allocating color space RGB\n"); return NULL; } // Allocate memory for image data bitmapData = (uint32_t *)malloc(bufferLength); if(!bitmapData) { NSLog(@"Error allocating memory for bitmap\n"); CGColorSpaceRelease(colorSpace); return NULL; } //Create bitmap context context = CGBitmapContextCreate(bitmapData, width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedLast); // RGBA if(!context) { free(bitmapData); NSLog(@"Bitmap context not created"); } CGColorSpaceRelease(colorSpace); return context; }
2.转换成需要打印的data
-(NSData*) imageToThermalData:(UIImage *)image{ CGImageRef imageRef = image.CGImage; // Create a bitmap context to draw the uiimage into CGContextRef context = [self newBitmapRGBA8ContextFromImage:imageRef]; if(!context) { return NULL; } size_t width = CGImageGetWidth(imageRef); size_t height = CGImageGetHeight(imageRef); CGRect rect = CGRectMake(0, 0, width, height); // Draw image into the context to get the raw image data CGContextDrawImage(context, rect, imageRef); // Get a pointer to the data uint32_t *bitmapData = (uint32_t *)CGBitmapContextGetData(context); if(bitmapData) { uint8_t *m_imageData = (uint8_t *) malloc(width * height/8 + 8*height/8); memset(m_imageData, 0, width * height/8 + 8*height/8); int result_index = 0; for(int y = 0; (y + 24) < height; ) { m_imageData[result_index++] = 27; m_imageData[result_index++] = 51; m_imageData[result_index++] = 0; m_imageData[result_index++] = 27; m_imageData[result_index++] = 42; m_imageData[result_index++] = 33; m_imageData[result_index++] = width%256; m_imageData[result_index++] = width/256; for(int x = 0; x < width; x++) { int value = 0; for (int temp_y = 0 ; temp_y < 8; ++temp_y) { uint8_t *rgbaPixel = (uint8_t *) &bitmapData[(y+temp_y) * width + x]; uint32_t gray = 0.3 * rgbaPixel[RED] + 0.59 * rgbaPixel[GREEN] + 0.11 * rgbaPixel[BLUE]; if (gray < 127) { value += 1<<(7-temp_y)&255; } } m_imageData[result_index++] = value; value = 0; for (int temp_y = 8 ; temp_y < 16; ++temp_y) { uint8_t *rgbaPixel = (uint8_t *) &bitmapData[(y+temp_y) * width + x]; uint32_t gray = 0.3 * rgbaPixel[RED] + 0.59 * rgbaPixel[GREEN] + 0.11 * rgbaPixel[BLUE]; if (gray < 127) { value += 1<<(7-temp_y%8)&255; } } m_imageData[result_index++] = value; value = 0; for (int temp_y = 16 ; temp_y < 24; ++temp_y) { uint8_t *rgbaPixel = (uint8_t *) &bitmapData[(y+temp_y) * width + x]; uint32_t gray = 0.3 * rgbaPixel[RED] + 0.59 * rgbaPixel[GREEN] + 0.11 * rgbaPixel[BLUE]; if (gray < 127) { value += 1<<(7-temp_y%8)&255; } } m_imageData[result_index++] = value; } m_imageData[result_index++] = 13; m_imageData[result_index++] = 10; y += 24; } NSMutableData *data = [[NSMutableData alloc] initWithCapacity:0]; [data appendBytes:m_imageData length:result_index]; free(bitmapData); return data; } else { NSLog(@"Error getting bitmap pixel data\n"); } CGContextRelease(context); return nil ; }
这两个方法 用到了一个 小枚举
typedef enum { ALPHA = 0, BLUE = 1, GREEN = 2, RED = 3 } PIXELS;
上面生成的data 就可以直接调用打印方法
- (NSData *)getDataForPrint{ UIImage *image = [UIImage_catagory imageOfQRFromURL:@"C0000001POS031502761784548" codeSize:300]; image = [UIImage_catagory excludeFuzzyImageFromCIImage:image.CIImage size:300]; NSData *data = [self imageToThermalData:image]; [self.socket writeData:data withTimeout:-1 tag:1]; return data; }
生成二维码的方法,我就不贴了 到处都是.
需要的话我贴一个 OC 或者 swift的demo. 做完之后发现 也很简单
有问题大家进一个群 107548668 欢迎交流