訪客機(jī)或消費(fèi)機(jī)等設(shè)備有連接打印機(jī)打印小票的需求,本篇記錄一下設(shè)備連接USB打印機(jī)打印小票內(nèi)容的功能實(shí)現(xiàn)。
其中涉及的工具類(lèi)包括Other.java、Command.java、PrinterCommand.java和PrinterController.java
連接USB打印機(jī)
1. Other.java類(lèi)
該工具類(lèi)中只包含一個(gè)方法,用于將byteArrays數(shù)據(jù)轉(zhuǎn)換為Bytes數(shù)據(jù)。
public class Other {
public static byte[] byteArraysToBytes(byte[][] data) {
int length = 0;
for (byte[] datum : data) {
length += datum.length;
}
byte[] send = new byte[length];
int k = 0;
for (byte[] datum : data) {
for (byte b : datum) {
send[k++] = b;
}
}
return send;
}
}
2. Command.java類(lèi)
該類(lèi)主要定義打印機(jī)所需的指令。
/**
* 打印機(jī)指令
*/
public class Command {
//打印機(jī)初始化
public static byte[] ESC_Init = new byte[] {0x1b, 0x40 };
//實(shí)時(shí)狀態(tài)傳送指令
public static byte[] DLE_eot = new byte[] {0x10, 0x04, 0x04 };
//實(shí)時(shí)狀態(tài)傳送指令 查詢1 打印機(jī)
public static final byte[] DLE_eot1 = new byte[] {0x10, 0x04, 0x01};
//實(shí)時(shí)狀態(tài)傳送指令 查詢2 脫機(jī)
public static final byte[] DLE_eot2 = new byte[] {0x10, 0x04, 0x02};
//實(shí)時(shí)狀態(tài)傳送指令 查詢3 錯(cuò)誤
public static final byte[] DLE_eot3 = new byte[] {0x10, 0x04, 0x03};
//實(shí)時(shí)狀態(tài)傳送指令 查詢4 卷紙傳感器
public static final byte[] DLE_eot4 = new byte[] {0x10, 0x04, 0x04};
//獲取打印機(jī)打印狀態(tài) 測(cè)試
public static final byte[] ESC_STATUS = new byte[] {0x1B, 0x76};
//設(shè)置對(duì)齊模式
public static byte[] ESC_Align = new byte[] {0x1b, 0x61, 0x00 };
//設(shè)置字體加粗
public static final byte[] ESC_BOLD = new byte[] {0x1b, 0x45,0x01};
//橫向放大
public static final byte[] ESC_GS_H = new byte[] {0x1b, 0x55, 0x00};
//縱向放大
public static final byte[] ESC_GS_O = new byte[] {0x1b, 0x56, 0x00};
//橫向縱向放大
public static final byte[] ESC_GS_O_H = new byte[] {0x1b, 0x57, 0x00};
//換行
public static final byte[] ESC_WRAP= new byte[] {0x0A};
//回車(chē)
public static final byte[] ESC_ENTER= new byte[] {0x0D};
//走紙N行, 最后一位是行數(shù),默認(rèn)一行
public static final byte[] ESC_Paper = new byte[] {0x1B, 0x64, 0x01};
//簡(jiǎn)體中文字符集
public static final byte[] ESC_Chinese = new byte[] {0x1c, 0x26, 0x1b, 0x74, 0x00};
}
3. PrinterCommand.java類(lèi)
該類(lèi)提供打印功能,包括打印機(jī)初始化、設(shè)置打印格式等功能。
public class PrinterCommand {
/**
* 打印機(jī)初始化
*/
public byte[] POS_Set_PrtInit() {
return Other.byteArraysToBytes(new byte[][]{Command.ESC_Init});
}
/**
* 設(shè)置對(duì)齊模式
*
* @param align 0 左對(duì)齊, 1 居中, 2 右對(duì)齊
* @return 打印機(jī)字節(jié)
*/
public byte[] POS_S_Align(int align) {
if (align < 0 || align > 2) {
return null;
}
byte[] data = Command.ESC_Align;
data[2] = (byte) align;
return data;
}
/**
* 換行
* @param count 換行行數(shù)1-10
* @return
*/
public byte[] POS_S_PrintLine(int count){
if(count <= 0) count = 1;
if(count >= 10) count = 10;
byte[] data = Command.ESC_Paper;
data[2] = (byte) count;
return data;
}
/**
* 橫向放大
* @param font_size 放大尺寸
* @return
*/
public byte[] POS_S_GS_H(int font_size) {
if (font_size <= 0) font_size = 0;
if (font_size >= 7) font_size = 7;
byte[] data = Command.ESC_GS_H;
data[2] = (byte) (font_size );
return data;
}
/**
* 縱向放大
* @param font_size 放大尺寸
* @return
*/
public byte[] POS_S_GS_O(int font_size) {
if (font_size <= 0) font_size = 0;
if (font_size >= 7) font_size = 7;
byte[] data = Command.ESC_GS_O;
data[2] = (byte) (font_size );
return data;
}
/**
* 橫向縱向同時(shí)放大
* @param font_size 放大尺寸
* @return
*/
public byte[] POS_S_GS_O_H(int font_size) {
if (font_size <= 0) font_size = 0;
if (font_size >= 7) font_size = 7;
byte[] data = Command.ESC_GS_O_H;
data[2] = (byte) ( font_size );
return data;
}
/**
* 加粗
* @return
*/
public byte[] Set_Bold(){
byte[] data = Command.ESC_BOLD;
data[2] = (byte) (1);
return data;
}
/**
* 換行
* @return
*/
public byte[] POS_Set_Enter(){
return Command.ESC_ENTER;
}
}
4. PrinterController.java類(lèi)
該類(lèi)為打印控制類(lèi),提供了打印機(jī)連接、打印內(nèi)容、監(jiān)聽(tīng)usb打印機(jī)連接狀態(tài)等接口。
public class PrinterController extends BroadcastReceiver {
private static final String TAG = "PrinterController";
private Context appContext;
private UsbManager mUsbManager;
// 我們的發(fā)送端、打印機(jī)的接收端
private UsbEndpoint epOut;
// 我們的讀取端、打印機(jī)的發(fā)送端
private UsbEndpoint epIn;
private UsbInterface usbIf;
private UsbDeviceConnection conn;
private UsbDevice dev;
private static final int LINE_BYTE_SIZE = 48;
public static final String PRINTER = "printer";
public static final String MICRO_PRINTER = "micro printer";
public static final String FMT_YYYY_MM_DD_HH_MM2 = "yyyy-MM-dd HH:mm";
public static final String ACTION_USB_PERMISSION = "com.zj.usbconn.USB";
private static final PrinterController INSTANCE = new PrinterController();
public PrinterController() {}
public static PrinterController getInstance() {
return INSTANCE;
}
public void getPrinterContext(Context context) {
appContext = context;
mUsbManager = (UsbManager) appContext.getSystemService(Context.USB_SERVICE);
}
public final synchronized void connect(UsbDevice dev) {
close();
PrinterController.getInstance().dev = dev;
if (!this.isHasPermission(dev)) {
this.getPermission(dev);
Toast.makeText(appContext, "打印機(jī)無(wú)權(quán)限", Toast.LENGTH_SHORT).show();
Log.i(TAG, "打印機(jī)無(wú)權(quán)限getPermission");
} else {
Toast.makeText(appContext, "打印機(jī)連接成功", Toast.LENGTH_SHORT).show();
Log.i(TAG, "打印機(jī)連接成功connect");
}
}
public final synchronized void tryConnect() {
close();
boolean hasPrinters = false;
for (UsbDevice device : mUsbManager.getDeviceList().values()) {
String name = device.getProductName();
if (name == null || name.isEmpty()) {
continue;
}
name = name.toLowerCase(Locale.CHINA);
if (name.contains(PRINTER) || name.contains(MICRO_PRINTER)) {
hasPrinters = true;
connect(device);
break;
}
}
if (!hasPrinters){
Log.d(TAG, "未搜索到USB打印機(jī)!");
Toast.makeText(appContext, "未搜索到USB打印機(jī)!", Toast.LENGTH_SHORT).show();
}
}
public final synchronized boolean isHasPermission(UsbDevice dev) {
return mUsbManager.hasPermission(dev);
}
public final synchronized void getPermission(UsbDevice dev) {
if (!isHasPermission(dev)) {
PendingIntent pi = PendingIntent.getBroadcast(appContext, 0, new Intent(ACTION_USB_PERMISSION), 0);
mUsbManager.requestPermission(dev, pi);
Toast.makeText(appContext, "打印機(jī)請(qǐng)求權(quán)限!", Toast.LENGTH_SHORT).show();
Log.i(TAG, "打印機(jī)請(qǐng)求權(quán)限r(nóng)equestPermission");
} else {
Toast.makeText(appContext, "打印機(jī)連接成功!", Toast.LENGTH_SHORT).show();
Log.i(TAG, "打印機(jī)連接成功");
}
}
public final synchronized void sendByte(byte[] bits) {
if (bits == null) {
return;
}
// 打印機(jī)正常
if (epOut != null && usbIf != null && conn != null) {
conn.bulkTransfer(epOut, bits, bits.length, 0);
return;
}
Log.d(TAG, "PrinterController sendByte: initDeviceUsb()");
UsbDeviceConnection tmpConn = initDeviceUsb();
if (tmpConn != null && tmpConn.claimInterface(usbIf, true)) {
tmpConn.bulkTransfer(epOut, bits, bits.length, 0);
}
}
public UsbDeviceConnection initDeviceUsb() {
if (conn == null) {
conn = mUsbManager.openDevice(dev);
}
if (dev == null || dev.getInterfaceCount() == 0) {
return null;
}
usbIf = dev.getInterface(0);
if (usbIf == null) {
return null;
}
if (usbIf.getEndpointCount() == 0) {
return null;
}
Log.d(TAG, "PrinterController sendByte: i in 0 until it.endpointCount");
for (int i = 0; i < usbIf.getEndpointCount(); i++) {
UsbEndpoint endpoint = usbIf.getEndpoint(i);
if (endpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK && endpoint.getDirection() == UsbConstants.USB_DIR_OUT) {
epOut = endpoint;
break;
}
}
return conn;
}
private final synchronized void sendByteDirectly(byte[] bits) {
UsbDeviceConnection tmpConn = initDeviceUsb();
if (tmpConn == null) {
return;
}
if (tmpConn.claimInterface(usbIf, true)) { // 獲取USB設(shè)備接口的控制權(quán)
tmpConn.bulkTransfer(epOut, bits, bits.length, 0); // 批量數(shù)據(jù)傳輸
}
}
// 讀打印機(jī)狀態(tài), 對(duì)海外打印機(jī)無(wú)用,只對(duì)國(guó)內(nèi)打印機(jī)有用
// 不能頻繁查詢,會(huì)導(dǎo)致打印機(jī)無(wú)響應(yīng)
public int readStatus(byte[] command) {
sendByteDirectly(command);
int count = (usbIf != null ? usbIf.getEndpointCount() : -1);
Log.d(TAG, "PrinterController readStatus: " + count);
if (epIn == null) {
for (int i = 0; i < count; i++) {
UsbEndpoint ep = usbIf != null ? usbIf.getEndpoint(i) : null;
if (ep != null && ep.getType() != UsbConstants.USB_ENDPOINT_XFER_BULK) return -1;
if (ep != null && ep.getDirection()!= UsbConstants.USB_DIR_IN) continue; //不是讀端就跳過(guò),一共2個(gè)端口,一個(gè)讀一個(gè)寫(xiě)
//讀端初始化
if (epIn == null) {
epIn = ep;
Log.d(TAG, "PrinterController read:epIn init");
}
break;
}
}
UsbEndpoint tmpEpIn = epIn;
UsbDeviceConnection tmpConn = conn;
if (tmpEpIn == null || tmpConn == null) return -1;
byte[] retData = new byte[64];
int readLen = tmpConn.bulkTransfer(tmpEpIn, retData, retData.length, 3000);
Log.d(TAG, "PrinterController read:readLen " + readLen);
if (readLen == -1) return -1;
Log.d(TAG, "PrinterController read:retData != -1");
byte[] realData = new byte[65];
System.arraycopy(retData, 0, realData, 0, retData.length);
Log.d(TAG, "PrinterController read:readLen " + realData[0]);
return realData[0];
}
// 一行打印2段內(nèi)容
public String printTwoData(String leftText, String rightText) {
int leftTextLength = leftText.getBytes().length;
int rightTextLength = rightText.getBytes().length;
StringBuilder sb = new StringBuilder();
sb.append(leftText);
// 計(jì)算兩側(cè)文字中間的空格
int marginBetweenMiddleAndRight = LINE_BYTE_SIZE - leftTextLength - rightTextLength;
for (int i = 0; i < marginBetweenMiddleAndRight; i++) {
sb.append(" ");
}
sb.append(rightText);
// 處理信息過(guò)長(zhǎng)問(wèn)題
int outCome = (leftTextLength + rightTextLength) / LINE_BYTE_SIZE; // 結(jié)果
int remainder = (leftTextLength + rightTextLength) % LINE_BYTE_SIZE;// 余數(shù)
// 結(jié)果不為0并且還有余數(shù),表明換行了,此時(shí)需要在后面加空白
// 計(jì)算出離右邊的距離
int marginRightBlank = (outCome > 0 && remainder > 0) ? (((outCome + 1) * LINE_BYTE_SIZE) - leftTextLength - rightTextLength) : 0;
for (int i = 0; i < marginRightBlank; i++) {
sb.append(" ");
}
return sb.toString();
}
// 一行打印3段內(nèi)容
public String printThreeData(String leftText, String centerText, String rightText) {
int leftTextLength = leftText.getBytes().length;
int centerTextLength = centerText.getBytes().length;
int rightTextLength = rightText.getBytes().length;
StringBuilder sb = new StringBuilder();
sb.append(leftText);
// 計(jì)算兩側(cè)文字中間的空格
int marginBetweenMiddleAndRight = LINE_BYTE_SIZE - leftTextLength - centerTextLength - rightTextLength;
for (int i = 0; i < marginBetweenMiddleAndRight/2; i++) {
sb.append(" ");
}
sb.append(centerText);
for (int i = 0; i < marginBetweenMiddleAndRight/2; i++) {
sb.append(" ");
}
sb.append(rightText);
// 處理信息過(guò)長(zhǎng)問(wèn)題
int outCome = (leftTextLength + centerTextLength + rightTextLength) / LINE_BYTE_SIZE; // 結(jié)果
int remainder = (leftTextLength + centerTextLength + rightTextLength) % LINE_BYTE_SIZE;// 余數(shù)
// 結(jié)果不為0并且還有余數(shù),表明換行了,此時(shí)需要在后面加空白
// 計(jì)算出離右邊的距離
int marginRightBlank = (outCome > 0 && remainder > 0) ? (((outCome + 1) * LINE_BYTE_SIZE) - leftTextLength - centerTextLength - rightTextLength) : 0;
for (int i = 0; i < marginRightBlank; i++) {
sb.append(" ");
}
return sb.toString();
}
public static String date2String(Date date, String format) {
SimpleDateFormat sdf = new SimpleDateFormat(format);
return sdf.format(date);
}
public final synchronized void close() {
if (conn != null) {
conn.close();
epOut = null;
epIn = null;
usbIf = null;
conn = null;
dev = null;
}
}
// 監(jiān)聽(tīng)usb設(shè)備連接狀態(tài)廣播,
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "onReceive intent = " + intent);
// USB設(shè)備斷開(kāi)廣播
if (intent.getAction() == UsbManager.ACTION_USB_DEVICE_DETACHED){
Toast.makeText(appContext, "打印機(jī)連接斷開(kāi)", Toast.LENGTH_SHORT).show();
}
}
}
5. 打印機(jī)使用
添加好打印工具類(lèi)后就可以再M(fèi)ainActivity中連接、使用打印機(jī)了。MainActivity中添加了兩個(gè)按鈕,分別用于實(shí)現(xiàn)連接打印機(jī)和開(kāi)始打印2個(gè)功能。
<MainActivity.java>
public class MainActivity extends AppCompatActivity {
private static final String TAG = "PrintingActivity";
private PrinterController printer;
private final PrinterCommand commands = new PrinterCommand();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 初始化打印工具類(lèi)
printer = PrinterController.getInstance();
printer.getPrinterContext(getApplicationContext());
// 動(dòng)態(tài)注冊(cè) usb設(shè)備斷開(kāi) 廣播
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
registerReceiver(printer, intentFilter);
initView();
}
private void initView(){
// 連接打印機(jī)
findViewById(R.id.connect_btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
initPrint();
}
});
// 開(kāi)始打印
findViewById(R.id.print_btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startPrinting();
}
});
}
// 連接打印機(jī)
private void initPrint(){
printer.close();
printer.tryConnect();
}
// 開(kāi)始打印
private void startPrinting(){
// 1. 打印之前先建立連接
printer.tryConnect();
// 2. 檢查狀態(tài)是否正常
int status = printer.readStatus(Command.DLE_eot4);
if (status != 18){
Toast.makeText(this, "打印機(jī)異常,請(qǐng)檢查打印機(jī)!", Toast.LENGTH_SHORT).show();
Log.e(TAG,"printer error : status = " + status);
return;
}else {
Toast.makeText(this, "打印機(jī)正常!", Toast.LENGTH_SHORT).show();
Log.e(TAG,"printer over : status = " + status);
}
// 3. 自定義打印內(nèi)容
customPrint();
// 4. 關(guān)閉打印機(jī)
printer.close();
}
/**
* 自定義打印格式和打印內(nèi)容
*/
private void customPrint(){
printOneData("北京稻香村", 1, false, true, "all", 2, true, 2);
printTwoData("打印時(shí)間:", printer.date2String(new Date(), printer.FMT_YYYY_MM_DD_HH_MM2), 1, false, true, "all", 1, true, 1);
printOneData("-----------------------------------------------", 1, false, false, "", 0, true, 1);
printThreeData("單價(jià)", "數(shù)量", "金額", 1, false, false, "", 0, true, 1);
printOneData("-----------------------------------------------", 1, false, false, "", 0, true, 1);
printThreeData("蛋黃酥", "1", "¥50.00", 1, false, false, "", 0, true, 1);
printOneData("-----------------------------------------------", 1, false, false, "", 0, true, 1);
printTwoData("實(shí)收金額", "¥50.00", 1, true, false, "", 0, true, 4);
}
/**
* 設(shè)置打印內(nèi)容:一行只有1個(gè)打印內(nèi)容
* @param text 打印的文本
* @param align 對(duì)齊模式 0#左對(duì)齊, 1#居中, 2#右對(duì)齊
* @param isBold 是否加粗
* @param isEnlarge 是否放大
* @param direction 放大方向 horizontal#水平放大, vertical#垂直放大, all#整體放大
* @param multiple 放大倍數(shù) [0,7]
* @param isEnter 是否回車(chē)
* @param enterLine 換行數(shù) [1.10]
*/
private void printOneData(String text, int align, boolean isBold, boolean isEnlarge, String direction, int multiple, boolean isEnter, int enterLine){
printer.sendByte(commands.POS_Set_PrtInit()); // 初始化格式
printer.sendByte(commands.POS_S_Align(align)); // 對(duì)齊模式
if (isBold){
printer.sendByte(commands.Set_Bold()); //加粗
}
if (isEnlarge){
if ("horizontal".equals(direction)){
printer.sendByte(commands.POS_S_GS_H(multiple)); // 橫向放大
}else if ("vertical".equals(direction)){
printer.sendByte(commands.POS_S_GS_O(multiple)); // 縱向放大
}else if ("all".equals(direction)){
printer.sendByte(commands.POS_S_GS_O_H(multiple)); // 放大字體
}
}
printer.sendByte(text.getBytes(Charset.forName("GBK"))); // 設(shè)置打印內(nèi)容
// printer.sendByte(commands.POS_Set_Enter()); // 回車(chē)
if (isEnter) {
printer.sendByte(commands.POS_S_PrintLine(enterLine)); // 換行
}
}
/**
* 設(shè)置打印內(nèi)容:一行有2個(gè)打印內(nèi)容
* @param leftText 左側(cè)打印的文本
* @param rightText 右側(cè)打印的文本
* @param align 對(duì)齊模式 0#左對(duì)齊, 1#居中, 2#右對(duì)齊
* @param isBold 是否加粗
* @param isEnlarge 是否放大
* @param direction 放大方向 horizontal#水平放大, vertical#垂直放大, all#整體放大
* @param multiple 放大倍數(shù) [0,7]
* @param isEnter 是否回車(chē)
* @param enterLine 換行數(shù) [1.10]
*/
private void printTwoData(String leftText, String rightText, int align, boolean isBold, boolean isEnlarge, String direction, int multiple, boolean isEnter, int enterLine){
printer.sendByte(commands.POS_Set_PrtInit()); // 初始化格式
printer.sendByte(commands.POS_S_Align(align)); // 對(duì)齊模式
if (isBold){
printer.sendByte(commands.Set_Bold()); //加粗
}
if (isEnlarge){
if ("horizontal".equals(direction)){
printer.sendByte(commands.POS_S_GS_H(multiple)); // 橫向放大
}else if ("vertical".equals(direction)){
printer.sendByte(commands.POS_S_GS_O(multiple)); // 縱向放大
}else if ("all".equals(direction)){
printer.sendByte(commands.POS_S_GS_O_H(multiple)); // 放大字體
}
}
printer.sendByte(printer.printTwoData(leftText, rightText).getBytes(Charset.forName("GBK"))); // 設(shè)置打印內(nèi)容
if (isEnter) {
printer.sendByte(commands.POS_S_PrintLine(enterLine)); // 換行
}
}
/**
* 設(shè)置打印內(nèi)容:一行有3個(gè)打印內(nèi)容
* @param leftText 左側(cè)打印的文本
* @param centerText 中間打印的文本
* @param rightText 右側(cè)打印的文本
* @param align 對(duì)齊模式 0#左對(duì)齊, 1#居中, 2#右對(duì)齊
* @param isBold 是否加粗
* @param isEnlarge 是否放大
* @param direction 放大方向 horizontal#水平放大, vertical#垂直放大, all#整體放大
* @param multiple 放大倍數(shù) [0,7]
* @param isEnter 是否回車(chē)
* @param enterLine 換行數(shù) [1.10]
*/
private void printThreeData(String leftText, String centerText, String rightText, int align, boolean isBold, boolean isEnlarge, String direction, int multiple, boolean isEnter, int enterLine){
printer.sendByte(commands.POS_Set_PrtInit()); // 初始化格式
printer.sendByte(commands.POS_S_Align(align)); // 對(duì)齊模式
if (isBold){
printer.sendByte(commands.Set_Bold()); //加粗
}
if (isEnlarge){
if ("horizontal".equals(direction)){
printer.sendByte(commands.POS_S_GS_H(multiple)); // 橫向放大
}else if ("vertical".equals(direction)){
printer.sendByte(commands.POS_S_GS_O(multiple)); // 縱向放大
}else if ("all".equals(direction)){
printer.sendByte(commands.POS_S_GS_O_H(multiple)); // 放大字體
}
}
printer.sendByte(printer.printThreeData(leftText, centerText, rightText).getBytes(Charset.forName("GBK"))); // 設(shè)置打印內(nèi)容
if (isEnter) {
printer.sendByte(commands.POS_S_PrintLine(enterLine)); // 換行
}
}
}