商米打印机驱动

简介

商米的V1、P1、T1内置了热敏打印机,允许App直接打印热敏小票出来。商米产品的内置打印机一共有2种规格:

  • 80mm宽,带切刀。兼容58mm,H10、T1搭载了这种打印机
  • 58mm宽,不带切刀。V1、V2搭载了这种打印机

App如何调用内置热敏打印机

App开发者可以使用3种方式调用内置热敏打印机:

1.通过蓝牙连接打印机
2.通过AIDL连接打印机
3.通过JS桥调用打印机

下面以V1设备为例,介绍两种调用方式,该方案也适用于P1、T1设备。

一、通过蓝牙连接调用

在V1的蓝牙设备列表中可以看到一个已经配对,且永远存在的蓝牙设备“ InnerPrinter”,这是由操作系统虚拟出来的打印机设备,实际并不存在,但它的通信方式与外置的蓝牙打印机几乎完全相同,是用来方便已经熟悉ESC/POS指令的开发者适配V1,这样他们就无需修改代码、快速迁移打印代码到V1了。

因此,如果开发者已经熟悉ESC/POS指令,甚至拥有的App已经针对ESC/POS指令完成了开发,或期望打印方法保持通用性,不要高度依赖商米设备的特征,则建议使用蓝牙连接方式。

如果还不熟悉ESC/POS指令,可以 点击这里 下载《ESC/POS指令说明》了解V1支持的指令集。

通过连接InnerPrinter虚拟打印机来完成打印的原理是:

1.与该蓝牙设备建立连接
2.将指令和文本内容拼接转码为Bytes
3.发送给InnerPrinter
4.底层打印服务驱动打印设备完成打印

关于如何连接“InnerPrinter”,开发者可以在网上搜索蓝牙连接的相关基础教程,为方便阅读,以下简要地说明了蓝牙设备连接的开发代码,便于快速上手。

以下效果为蓝牙打印Demo App打印的效果,点击这里下载Demo Apk及源码

1

其中包含5个关键操作:

1.在App的项目中添加蓝牙权限声明

 2.获取蓝牙设备BluetoothAdapter

  BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    if (mBluetoothAdapter == null && !mBluetoothAdapter.isEnabled()) {
       Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);  
       startActivityForResult(intent, REQUEST_ENABLE_BT);
    }

 3.获取虚拟蓝牙设备

  String innerprinter_address = "00:11:22:33:44:55";
    BluetoothDevice innerprinter_device = null;
    Set devices = mBluetoothAdapter.getBondedDevices();
    for(BluetoothDevice device : devices){
        if(device.getAddress().equals(innerprinter_address)){
              innerprinter_device = device;
         }
     }

 4.获取蓝牙套接字 BluetoothSocket

 UUID PRINTER_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
   BluetoothSocket mSocket = 
   innerprinter_device.createRfcommSocketToServiceRecord(PRINTER_UUID);

 5 .将已经拼接完的打印指令Bytes,通过sendData(byte [] bytes)方法发送给InnerPrinter

  OutputStream mOut = mSocket.getOutputStream();
   private void sendData(byte[] bytes) {
          if (mOut != null) {
              try {
                  mOut.write(bytes, 0, bytes.length);
                  mOut.flush();
              } catch (IOException e) {
                  Log.e("TAG", e.getMessage());
              } finally {
                 try {
                      mOut.close();
                  } catch (IOException e) { }
              }
          }
    }

 第5个关键操作中的byte[] bytes参数为待打印的小票内容,是通过“GBK”编码将文字转为Byte与指令Byte拼接在一起的内容。

 58mm宽打印机的每行含384个像素点,每个中文字符默认宽度为24像素,每个英文字符默认宽度为12像素,所以一行最多可以打印出16个中文字符或32个英文字符,每行打印后会自动换行,当然,你也可以通过打印“ ”字符强制换行。

 每个行的默认高度是30像素,你可以通过指令设置倍高倍宽来控制字的大小,通过对齐指令来控制每行的文字位置。

 请参考《ESC/POS指令说明》了解更多的控制指令细节。

 注意:可能会出现多个应用同时发起打印请求的问题,我们建议您如果通过虚拟蓝牙打印,打印数据最好一次性发送。

二、通过AIDL调用打印机

 背景

小票打印机的发明者是EPSON,ESC/POS指令就是由EPSON设计、定义的,在早在Android之前就已经出现,在互联网普及之前就已经广泛应用于餐饮、零售行业,至今有15年以上的历史了。

 因此指令中的数据处理方式比较底层,一些经历过传统软件开发的开发者可能在这些指令集上有自己封装和积累,但对于新兴的互联网开发者来说,无论是打印文字、图片、调整格式都会显得非常繁琐,需要投入一定的成本周期来适应。

 因此,在V1的开发者还可以通过使用Android标准的AIDL连接内置打印服务,调用打印机。

 AIDL是Android Interface Definition language的缩写,它是一种Android内部进程通信接口的描述语言,通过它我们可以定义进程间的通信接口。关于AIDL的使用方法,请开发者查看网上相关的文档查看相关说明。

 当然开发者也可以暂时跳过AIDL的原理,通过下文简短的说明及示例代码,快速了解调用方式。

 教程

首先需要下载AIDL文件及Demo,压缩文档中含有2部分内容:


1.Demo是我们准备的打印示例工程源码,以下效果为通过Demo打印的小票,T1。
2.AIDL文件是所有打印服务暴露的接口方法清单,需要整合到自己App源码项目中。

 以下为Demo App中具体的调用关键操作解读:

 1.在自己项目中添加资源文件中附带的AIDL文件:
      (1)在src下建立woyou.aidlservice.jiuiv5包

      (2)加入IWoyouService.aidl、ICallback.aidl2个文件。

 2.在控制打印的代码类中实现ServiceConnection。

  private ServiceConnection connService = new ServiceConnection() {
        @Override
        public void onServiceDisconnected(ComponentName name) {
                woyouService = null;
            }
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
                woyouService = IWoyouService.Stub.asInterface(service);
                setButtonEnable(true);
           }
        };  

 3.调用ApplicationContext.bindService(),并在ServiceConnection实现中进行传递。

 注意:bindservice是非阻塞调用,意味着调用完成后并没有立即绑定成功,必须以serviceConnected为准。

 Intent intent=new Intent(); 
 intent.setPackage("woyou.aidlservice.jiuiv5"); 
 intent.setAction("woyou.aidlservice.jiuiv5.IWoyouService"); 
 startService(intent); bindService(intent, connService, 
 Context.BIND_AUTO_CREATE);

 4.在ServiceConnection.onServiceConnected()实现中,你会接收一个IBinder实例(被调用的Service). 调用 IWoyouService.Stub.asInterface(service)将参数转换为IWoyouService类型。

 5.现在就可以调用IWoyouService接口中定义的各种方法进行打印了。这一步你总要检测到RemoteException异常,该异常在连接断开时被抛出,这里需要做处理。它只会被远程方法抛出。接口方法中ICallback可以传递null。断开连接时调用接口实例中的 ApplicationContext.unbindService() 。

 IWoyouService中封装了打印很多种数据的方法和控制格式的方法如:

 woyouService.printText("this is a text",callback); 
 woyouService.printTextWithFont("this is a text with font","",36,callback); 
 woyouService.setFontSize(24,callback); 
 woyouservice.printColumnsText("print a line of a table!",12,0,callback);

 该接口同时也包含打印ESC/POS拼装的数据的方法:

 woyouService.sendRAWData(bytes,callback);

 注意:可能会出现多个应用同时发起打印请求的问题,您如果通过AIDL打印,我们建议您使用事物打印。

三、通过HTML中的JS调用打印机

通过JS调用打印机本质上是通过JS桥在HTML中操作android原生的代码实现打印,通过蓝牙打印或者AIDL的方式实现打印。我们给开发者准备了一个 Demo,在Demo中,通过点击HTML中的一个按钮,调用在java中写好的一个方法,让后通过AIDL的方式调用打印机打印一个自检小票。

以下为具体的调用流程:

 1.在HTML中定义android中要使用的方法。

  document.getElementsByTagName('a')[0].addEventListener('click', function(){
          
          var a = "wellcome to sunmi";
                  javascript:lee.funAndroid(a);   
          return false;
      }, false);      

 2.初始化WebView.

    WebView mWebView = (WebView) findViewById(R.id.wv_view);
    // 设置编码
    mWebView.getSettings().setDefaultTextEncodingName("utf-8");
    // 支持js
    mWebView.getSettings().setJavaScriptEnabled(true);
    mWebView.setWebChromeClient(new WebChromeClient());

 初始化打印服务

Intent intent = new Intent();
intent.setPackage("woyou.aidlservice.jiuiv5");
intent.setAction("woyou.aidlservice.jiuiv5.IWoyouService");
startService(intent);//Start printer service
bindService(intent, connService, Context.BIND_AUTO_CREATE);

 3.给WebView添加监听,在onPageFinished 回调方法中调用WebView.addJavascriptInterface(new JsObject(), ‘lee’);在JsObject类中通过 @JavascriptInterface定义在HTML中指定要操作的方法,在方法中通过调用AIDL打印方式打印一张小票。

 mWebView.setWebViewClient(new WebViewClientDemo());//添加一个页面相应监听类 class WebViewClientDemo extends WebViewClient {      @Override      public boolean shouldOverrideUrlLoading(WebView view, String url) {         // 当打开新链接时,使用当前的 WebView,不会使用系统其他浏览器          view.loadUrl(url);          return true;        }       @Override       public void onPageFinished(WebView view, String url) {       super.onPageFinished(view, url);          /**           * 注册JavascriptInterface,其中"lee"的名字随便取,如果你用"lee",那么在html中只要用  lee.方法名()           * 即可调用MyJavascriptInterface里的同名方法,参数也要一致           */            mWebView.addJavascriptInterface(new JsObject(), "lee");                 }         }                           class JsObject {                 @JavascriptInterface                 public void funAndroid(final String i) {                         Toast.makeText(getApplicationContext(), "calling the local method funAndroid by JS. " + i,                         Toast.LENGTH_SHORT).show();                                                          try {                                 woyouService.printerSelfChecking(callback);//Using AIDL to print something.                          } catch (RemoteException e) {                                 e.printStackTrace();                         }                 }         } 

 4.加载HTML文件,在点击HTML中的按钮的时候就会打印一张小票了

// 载入包含js的html
mWebView.loadData("", "text/html", null);
mWebView.loadUrl("file:///android_asset/test.html");//这里是您业务html所在的网页

 关于通过jar包调用打印机

 由于我们打印服务的升级,这个方式的调用在打印服务版本2.0.0以后都不再兼容,如果您已经使用了该方式进行打印 ,我们建议通过获取打印机版本号判断是否大于或等于’2.0.0’来兼容不同版本的打印服务,您可能会用到如下类似的代码:

    ... //绑定打印服务成功后得到一个IWoyouService类型的变量woyouService
    String version=woyouService.getServiceVersion(); //获取到打印服务的版本号
    if(version.compareTo("2.0.0")>=0){  //判断是否是新的打印服务
      ... //AIDL的方式调用打印服务
    }else{
      ... //依旧是通过jar包来调用打印服务
    }