Android 蓝牙/wifi云打印机 ESC/POS热敏打印机打印(ESC/POS指令篇)
上一篇主要介绍了如何通过蓝牙连接到打印机,这一篇,我们就介绍向打印机发送打印指令,来打印字符和图片我们先来看一下最终票据打印效果图:一、ESC/POS指令ESC/POS指令体系是由EPSON发明的一套专有POS打印机指令系统,市面上绝大部分打印机兼容ESC/POS指令。由于我使用的是佳博的蓝牙打印机,我们来看一下佳博打印机背后的型号说明:上图可见,打印机是58mm纸宽的打印机,支持USB和蓝牙连接
上一篇主要介绍了如何通过蓝牙打印机和wifi云打印机的连接与数据发送,这一篇,我们就介绍向打印机发送打印指令,来打印字符和图片。
由于公司暂且买了两台打印机,一台佳博GP-58MIII,一台GP-SH584。
我们先来看一下最终票据打印效果图:
一、ESC/POS指令
ESC/POS指令体系是由EPSON发明的一套专有POS打印机指令系统,市面上绝大部分打印机兼容ESC/POS指令。
由于我使用的是佳博的蓝牙打印机,我们来看一下佳博打印机背后的型号说明:
上图可见,两台打印机是58mm纸宽的打印机,一台支持USB和蓝牙连接,一台支持USB和wifi云,并且都支持ESC/POS命令。
二、常用的打印命令
手机通过蓝牙或者ip端口向打印机发送的都是纯字节流。打印机通过遵循ESC/POS指令控制打印。
2.1 初始化打印机
指令:该指令会清除打印缓冲区中的数据,但是接收缓冲区的数据并不会清除,一般开始打印的时候需要调用。
ASCII码 ESC @
十进制码 27 64
十六进制码 1B 40
佳博SDK将ESC指令封装成了一个EsCommand类,对应的代码如下:
EscCommand esc = new EscCommand();
esc.addInitializePrinter();//初始化打印机
//SDK源码内部
public void addInitializePrinter() {
byte[] command = new byte[]{27, 64};
this.addArrayToCommand(command);
}
//此处省略了部分核心代码
outputStream.write(esc.getCommand , offset, len);
...
2.2 打印文本
没有对应指令,直接输出文本
// 打印文字
esc.addText("发货单\n");
public void addText(String text) {
this.addStrToCommand(text);
}
2.3 打印多列文本
通用打印纸都是有固定宽度的,比如58mm打印纸最大宽度为384个像素店,80mm打印纸最大宽度为576个像素点。经过大量测试结果表明,一般最大字节数是32个字节(总宽度 = 左侧宽度 + 右侧宽度)
/**
* 左侧宽度 + 右侧宽度 = 打印纸总宽度
* 打印纸一行最大的字节
*/
private static final int LINE_BYTE_SIZE = 32;
/**
* 打印三列时,中间一列的中心线距离打印纸左侧的距离
*/
private static final int LEFT_LENGTH = 16;
/**
* 打印三列时,中间一列的中心线距离打印纸右侧的距离
*/
private static final int RIGHT_LENGTH = 16;
/**
* 打印四列时,第一列居左,第二列居中,第四列居右,第三列位于第二列和第四列中间
* 第三列距离第二列的距离
*/
private static final int MIDDLE_RIGHT_MIDDLE_LENGTH = 8;
/**
* 打印四列时,第一列居左,第二列居中,第四列居右,第三列位于第二列和第四列中间
* 第三列距离第四列的距离
*/
private static final int MIDDLE_RIGHT_MIDDLE_RIGHT = 8;
/**
* 打印三列时,第一列汉字最多显示几个文字
*/
private static final int LEFT_TEXT_MAX_LENGTH = 5;
当我们打印两列时,一列居左,一列居右;打印三列时,一列居左,一列居中,一列居右。那两列之间的空格,我们用下面的方法可以完美的计算出来剩余空格。这样就不用手动添加多个空格了。
/**
* 获取数据长度
*
* @param msg
* @return
*/
@SuppressLint("NewApi")
private static int getBytesLength(String msg) {
return msg.getBytes(Charset.forName("GB2312")).length;
}
/**
* 打满一列列
*
* @return
*/
@SuppressLint("NewApi")
public static String printOneFullData() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < LINE_BYTE_SIZE; i++) {
sb.append("-");
}
return sb.toString();
}
/**
* 打印两列
*
* @param leftText 左侧文字
* @param rightText 右侧文字
* @return
*/
@SuppressLint("NewApi")
public static String printTwoData(String leftText, String rightText) {
StringBuilder sb = new StringBuilder();
int leftTextLength = getBytesLength(leftText);
int rightTextLength = getBytesLength(rightText);
sb.append(leftText);
// 计算两侧文字中间的空格
int marginBetweenMiddleAndRight = LINE_BYTE_SIZE - leftTextLength - rightTextLength;
for (int i = 0; i < marginBetweenMiddleAndRight; i++) {
sb.append(" ");
}
sb.append(rightText);
return sb.toString();
}
/**
* 打印三列
*
* @param leftText 左侧文字
* @param middleText 中间文字
* @param rightText 右侧文字
* @return
*/
@SuppressLint("NewApi")
public static String printThreeData(String leftText, String middleText, String rightText) {
StringBuilder sb = new StringBuilder();
// 左边最多显示 LEFT_TEXT_MAX_LENGTH 个汉字 + 两个点
if (leftText.length() > LEFT_TEXT_MAX_LENGTH) {
leftText = leftText.substring(0, LEFT_TEXT_MAX_LENGTH) + "..";
}
int leftTextLength = getBytesLength(leftText);
int middleTextLength = getBytesLength(middleText);
int rightTextLength = getBytesLength(rightText);
sb.append(leftText);
// 计算左侧文字和中间文字的空格长度
int marginBetweenLeftAndMiddle = LEFT_LENGTH - leftTextLength - middleTextLength / 2;
for (int i = 0; i < marginBetweenLeftAndMiddle; i++) {
sb.append(" ");
}
sb.append(middleText);
// 计算右侧文字和中间文字的空格长度
int marginBetweenMiddleAndRight = RIGHT_LENGTH - middleTextLength / 2 - rightTextLength;
for (int i = 0; i < marginBetweenMiddleAndRight; i++) {
sb.append(" ");
}
// 打印的时候发现,最右边的文字总是偏右一个字符,所以需要删除一个空格
sb.delete(sb.length() - 1, sb.length()).append(rightText);
return sb.toString();
}
/**
* 打印四列
*
* @param leftText 左侧文字
* @param middleText 中间文字
* @param middleAndThirdText 中间与右侧中间的文字
* @param rightText 右侧文字
* @return
*/
@SuppressLint("NewApi")
public static String printFourData(String leftText, String middleText, String middleAndThirdText, String rightText) {
StringBuilder sb = new StringBuilder();
// 左边最多显示 LEFT_TEXT_MAX_LENGTH 个汉字 + 两个点
if (leftText.length() > LEFT_TEXT_MAX_LENGTH) {
leftText = leftText.substring(0, LEFT_TEXT_MAX_LENGTH) + "..";
}
int leftTextLength = getBytesLength(leftText);
int middleTextLength = getBytesLength(middleText);
int rightTextLength = getBytesLength(rightText);
int middleAndThirdTextLength = getBytesLength(middleAndThirdText);
sb.append(leftText);
// 计算左侧文字和中间文字的空格长度
int marginBetweenLeftAndMiddle = LEFT_LENGTH -
leftTextLength -
(middleTextLength > 1 ? middleTextLength / 2 : 1);
for (int i = 0; i < marginBetweenLeftAndMiddle; i++) {
sb.append(" ");
}
sb.append(middleText);
//计算中间文字和第三列文字的空格长度
int marginBetweenMiddleAndThirdText = MIDDLE_RIGHT_MIDDLE_LENGTH -
(middleTextLength > 1 ? middleTextLength / 2 : 1) -
(middleAndThirdTextLength > 1 ? middleAndThirdTextLength / 2 : 1);
for (int i = 0; i < marginBetweenMiddleAndThirdText; i++) {
sb.append(" ");
}
sb.append(middleAndThirdText);
// 计算第三列文字文字和右侧文字的空格长度
int marginBetweenThirdTextAndRight = MIDDLE_RIGHT_MIDDLE_RIGHT - (middleAndThirdTextLength > 1 ? middleAndThirdTextLength / 2 : 1) - rightTextLength;
for (int i = 0; i < marginBetweenThirdTextAndRight; i++) {
sb.append(" ");
}
// 打印的时候发现,最右边的文字总是偏右一个字符,所以需要删除一个空格
// sb.delete(sb.length() - 1, sb.length()).append(rightText);
sb.append(rightText);
return sb.toString();
}
2.4 设置对齐方式
指令:
ASCII码 ESC a n
十进制码 27 97 n
十六进制码 1B 61 n
n的取值,0左对齐,1中间对齐,2右对齐。
// 设置打印居中
esc.addSelectJustification(EscCommand.JUSTIFICATION.CENTER);
public void addSelectJustification(EscCommand.JUSTIFICATION just) {
byte[] command = new byte[]{27, 97, just.getValue()};
this.addArrayToCommand(command);
}
public static enum JUSTIFICATION {
LEFT(0),//左对齐
CENTER(1),//居中
RIGHT(2);//右对齐
private final int value;
private JUSTIFICATION(int value) {
this.value = value;
}
public byte getValue() {
return (byte)this.value;
}
}
2.5 字体倍高倍宽
指令:
ASCII码 ESC ! n
十进制码 27 33 n
十六进制码 1B 21 n
n的取值,0正常,16倍高,32倍宽,128下划线
// 设置为倍高倍宽
esc.addSelectPrintModes(EscCommand.FONT.FONTA, EscCommand.ENABLE.OFF, EscCommand.ENABLE.ON, EscCommand.ENABLE.ON, EscCommand.ENABLE.OFF);
// 取消倍高倍宽
esc.addSelectPrintModes(EscCommand.FONT.FONTA, EscCommand.ENABLE.OFF, EscCommand.ENABLE.OFF, EscCommand.ENABLE.OFF, EscCommand.ENABLE.OFF);
//SDK源码
public void addSelectPrintModes(EscCommand.FONT font, EscCommand.ENABLE emphasized, EscCommand.ENABLE doubleheight, EscCommand.ENABLE doublewidth, EscCommand.ENABLE underline) {
byte temp = 0;
if (font == EscCommand.FONT.FONTB) {
temp = 1;
}
if (emphasized == EscCommand.ENABLE.ON) {
temp = (byte)(temp | 8);
}
if (doubleheight == EscCommand.ENABLE.ON) {
temp = (byte)(temp | 16);
}
if (doublewidth == EscCommand.ENABLE.ON) {
temp = (byte)(temp | 32);
}
if (underline == EscCommand.ENABLE.ON) {
temp = (byte)(temp | 128);
}
byte[] command = new byte[]{27, 33, temp};
this.addArrayToCommand(command);
}
2.6 打印并走纸n行
指令:
ASCII码 ESC d n
十进制码 27 100 n
十六进制码 1B 64 n
//打印走纸多少行
esc.addPrintAndFeedLines((byte) 3);
//SDK源码
public void addPrintAndFeedLines(byte n) {
byte[] command = new byte[]{27, 100, n};
this.addArrayToCommand(command);
}
三、打印条码
3.1 条码高度指令
ASCII码 GS h n
十进制码 29 104 n
十六进制码 1D 68 n
n介于1到255之间,n默认值为162
public void addSetBarcodeHeight(byte height) {
byte[] command = new byte[]{29, 104, height};
this.addArrayToCommand(command);
}
3.2 条码宽度指令
ASCII码 GS w n
十进制码 29 119 n
十六进制码 1D 77 n
n介于2到6之间,默认n=3
public void addSetBarcodeHeight(byte height) {
byte[] command = new byte[]{29, 104, height};
this.addArrayToCommand(command);
}
3.3 打印条码
esc.addSetBarcodeHeight((byte) 80); //设置条码高度为80点
esc.addSetBarcodeWidth((byte) 3); // 设置条码单元宽度为3
esc.addCODE128(esc.genCode128("201811080001"));
public void addCODE128(String content) {
byte[] command = new byte[]{29, 107, 73, (byte)content.length()};
this.addArrayToCommand(command);
this.addStrToCommand(content, command[3]);
}
四、打印二维码
QRCode命令打印,此命令只在支持QRCode命令打印的机型才能使用。 在不支持二维码指令打印的机型上,则需要发送二维条码图片。
// 设置纠错等级
esc.addSelectErrorCorrectionLevelForQRCode((byte) 0x31);
// 设置qrcode模块大小
esc.addSelectSizeOfModuleForQRCode((byte) 10);
// 设置qrcode内容
esc.addStoreQRCodeData("www.whoot.com");
// 打印QRCode
esc.addPrintQRCode();
五、打印图片
很多小票上面会附上二维码或者logo,当打印机不支持二维码指令时,可以打印二维码的图片。由于热敏打印机只能打印黑白两色,所以首先需要把图片转成纯黑白的,再调用图片打印指令进行打印。
5.1 图片分辨率调整
58mm的热敏打印机,可打印区域最大宽度时384个像素点。80mm打印机可打印区域最大宽度为576个像素点。如果分辨率过大,超出了打印机可打印的最大宽度,那么超出部分将无法打印。所以在打印之前,我们需要调整图片的分辨率。
Bitmap b = BitmapFactory.decodeResource(App.getContext().getResources(), R.drawable.gprinter);
esc.addRastBitImage(b, 384, 0);
public void addRastBitImage(Bitmap bitmap, int nWidth, int nMode) {
if (bitmap != null) {
//因为横向每8个像素店组成一个字节,这里将位图宽度规范化为8的整数倍
int width = (nWidth + 7) / 8 * 8;
int height = bitmap.getHeight() * width / bitmap.getWidth();
Bitmap grayBitmap = GpUtils.toGrayscale(bitmap);
Bitmap rszBitmap = GpUtils.resizeImage(grayBitmap, width, height);
byte[] src = GpUtils.bitmapToBWPix(rszBitmap);
byte[] command = new byte[8];
height = src.length / width;
command[0] = 29;
command[1] = 118;
command[2] = 48;
command[3] = (byte)(nMode & 1);
command[4] = (byte)(width / 8 % 256);
command[5] = (byte)(width / 8 / 256);
command[6] = (byte)(height % 256);
command[7] = (byte)(height / 256);
this.addArrayToCommand(command);
byte[] codecontent = GpUtils.pixToEscRastBitImageCmd(src);
for(int k = 0; k < codecontent.length; ++k) {
this.Command.add(codecontent[k]);
}
} else {
Log.d("BMP", "bmp. null ");
}
}
/**
* 图片去色,返回灰度图片
*
*/
public static Bitmap toGrayscale(Bitmap bmpOriginal) {
int height = bmpOriginal.getHeight();
int width = bmpOriginal.getWidth();
Bitmap bmpGrayscale = Bitmap.createBitmap(width, height, Config.RGB_565);
Canvas c = new Canvas(bmpGrayscale);
Paint paint = new Paint();
ColorMatrix cm = new ColorMatrix();
cm.setSaturation(0.0F);
ColorMatrixColorFilter f = new ColorMatrixColorFilter(cm);
paint.setColorFilter(f);
c.drawBitmap(bmpOriginal, 0.0F, 0.0F, paint);
return bmpGrayscale;
}
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)