Android 蓝牙/wifi云打印机 ESC/POS热敏打印机打印(ESC/POS指令篇)

上一篇主要介绍了如何通过蓝牙打印机和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;
}

github地址:https://github.com/zoujin6649/PrinterDemo

  • 5
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在传统的PC应用中,通过直接调用打印机驱动程序的方式可以轻松地实现对蓝牙打印机的调用。但是,在Web应用和移动应用中,这种方式并不适合,所以我们需要寻找一种新的解决方法。 其中一种解决方案是使用JavaScript库或框架,比如原生JavaScript、jQuery和React等。这些工具可以为我们提供跨平台或跨浏览器的API,使得我们可以屏蔽底层的硬件驱动细节,从而更容易地实现对蓝牙打印机的调用。 实现蓝牙打印机的调用需要遵循ESC / POS打印机语言规范。ESC / POS是一种通用的打印机语言,被各种打印机采用,包括热敏和针式打印机。这种语言通过控制位、字符和命令来描述打印机的行为,每个命令都会发送给打印机的控制寄存器。 要实现对蓝牙打印机的调用,首先需要链接蓝牙打印机,这可以通过调用浏览器的Web Bluetooth API来完成。一旦与打印机建立连接,我们就可以通过发送ESC / POS命令来控制打印机,从而实现小票和图片的打印。 对于小票的打印,我们需要设计好小票模板并将其转换为ESC / POS命令。具体来说,需要先设置打印机的一些参数,比如字符大小和行距,然后将文本和表格等元素添加到模板中,最后将整个模板转换为ESC / POS命令并发送给打印机即可。 对于图片的打印,我们需要将图片转换为位图,并将其转换为ESC / POS命令。具体操作可以使用像CW浏览器的Canvas API在浏览器中渲染位图文件,然后将渲染后的位图文件转换为ESC / POS命令并发送给打印机即可。 总之,实现对蓝牙打印机的调用需要理解ESC / POS语言规范,并使用Web Bluetooth API和Canvas API等便利的工具来实现。虽然这种方法需要花费一些精力来学习和开发,但它可以轻松地在Web应用和移动应用中实现对蓝牙打印机的调用,具有很好的可移植性和开发效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值