admin管理员组

文章数量:1618187

本篇我们将讲解蓝牙打印机和wifi云打印机的连接与数据发送,下一篇讲解ESC/POS命令集

一、蓝牙打印机连接

1.蓝牙权限

2.初始化配置

3.发现设备

4.连接设备

4.1 作为Client连接

5.数据传输

二、wifi云打印机连接

2.1 添加设备

2.2 删除打印机

2.3 查询打印机列表

2.4 发送数据到打印机


一、蓝牙打印机连接

打印机的蓝牙连接方式是基于传统的蓝牙连接方式,手机作为客户端,打印机作为服务端。

我们先上效果图:

 

1.蓝牙权限

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<!-- If your app targets Android 9 or lower, you can declare 
    ACCESS_COARSE_LOCATION instead. -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH) != PackageManager.PERMISSION_GRANTED ||             
    ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_ADMIN) != PackageManager.PERMISSION_GRANTED || 
ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED || 
ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
    ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.BLUETOOTH, Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}, REQUEST_PERMISSION);
}

BLUETOOTH权限允许用户请求连接,接受连接和传输数据等,BLUETOOTH_ADMIN权限允许应用启动设备发现或操纵蓝牙设置。如果应用的目标版本是Android 9或者更低的版本,ACCESS_COARSE_LOCATION权限允许蓝牙扫描收集用户的位置信息,返回的是一个模糊的位置信息,此信息可能来自用户自己的设备,以及在商店和交通设施等位置使用蓝牙信标。Android 10开始,要使用蓝牙扫描位置信息需要申请ACCESS_FINE_LOCATION权限,返回的是精确的位置信息,除此之外,还需要开启GPS功能才行,不然不能搜索和连接其他蓝牙设备。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    LocationManager lm = (LocationManager) getSystemService(LOCATION_SERVICE);
    if (!lm.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
        showToast("请您先开启gps,否则蓝牙不可用");
        return;
    }
}

2.初始化配置

初始化设备本身的蓝牙适配器BluetoothAdapter,有两种方式:

//方式一:
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
//方式二:
BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();

整个系统只有一个蓝牙适配器,全局只有一个实例,如果返回null,则代表设备不支持蓝牙,如果设备支持蓝牙,再接着检查蓝牙是否打开:

if (bluetoothAdapter == null || !getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
    showToast("当前设备不支持蓝牙");
    finish();
    return;
} else {
    if (!bluetoothAdapter.isEnabled()) {
        //请求开启蓝牙
        Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult(enableIntent, REQUEST_ENABLE_BLE);
    } else {
        setPairingDevice();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                scanDevice();
            }
        }, 1000);
    }
}

成功打开蓝牙后就会回调到onActivityResult()中。除了这种主动的打开蓝牙,还可以监听BluetoothAdapter.ACTION_STATE_CHANGED广播,每当蓝牙状态发生变化时,此广播包含的值BluetoothAdapter.EXTRA_STATE,它包含新的蓝牙状态,可能的值:BluetoothAdapter.STATE_OFF和BluetoothAdapter.STATE_ON。

3.发现设备

设备发现是一个扫描过程,它会搜索局部区域内已开启蓝牙功能的设备,并请求与每台设备相关的某些信息。如果设备已开启可检测行,它会通过共享一些信息(例如设备名称、类及其唯一的MAC地址)来响应发现请求。扫描是一个耗时的过程,我们需要在异步执行,并且监听发现设备的广播。

Set<BluetoothDevice> devices = bluetoothAdapter.getBondedDevices();//获取已配对的设备
handler.postDelayed(new Runnable() {
    @Override
    public void run() {
        if (bluetoothAdapter.isDiscovering()) {
            bluetoothAdapter.cancelDiscovery();
        }
        bluetoothAdapter.startDiscovery();
    }
}, 1000);
private BroadcastReceiver discoveryReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        BluetoothDevice bluetoothDevice =     intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
        if (TextUtils.isEmpty(action) || bluetoothDevice == null) {
            return;
        }
        switch (action) {
            case BluetoothAdapter.ACTION_DISCOVERY_STARTED:
                Log.e("TAG", "正在搜索附近的蓝牙设备");
                break;
            case BluetoothAdapter.ACTION_DISCOVERY_FINISHED:
                Log.e("TAG", "搜索结束");
                break;
            case BluetoothDevice.ACTION_ACL_CONNECTED:
                Log.e("TAG", "与" + bluetoothDevice.getName() + "蓝牙已连接");
                break;
            case BluetoothDevice.ACTION_ACL_DISCONNECTED:
                Log.e("TAG", "与" + bluetoothDevice.getName() + "蓝牙连接已结束");
                break;
            case BluetoothDevice.ACTION_FOUND:
                Log.e("TAG", "发现了新设备");
                if (bluetoothDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
                    //Add
                }
                break;
            case BluetoothAdapter.ACTION_STATE_CHANGED:
                int blueState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
                switch (blueState) {
                    case BluetoothAdapter.STATE_OFF:
                        showToast("蓝牙已关闭");
                        finish();
                        break;
                    case BluetoothAdapter.STATE_ON:
                        showToast("蓝牙已开启");
                        Set<BluetoothDevice> devices = bluetoothAdapter.getBondedDevices();//获取已配对的设备
                        handler.postDelayed(new Runnable() {
                            @Override
                            public void run() {
                                if (bluetoothAdapter.isDiscovering()) {
                                    bluetoothAdapter.cancelDiscovery();
                                }
                                bluetoothAdapter.startDiscovery();
                            }
                        }, 1000);
                        break;
                }
            break;
        }
    }
};

注意:startDiscovery()只能扫描到那些状态被设为可发现的设备。安卓设备默认不可发现,要改变设备为可发现的状态,需要如下请求:

//无功能状态,查询扫描和页面扫描都无效,该状态下蓝牙模块既不能扫描其他设备,也不可见
//请求开启可见
Intent discoveryIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoveryIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivityForResult(discoveryIntent, REQUEST_DISCOVERABLE_BLE);

注意:startDiscovery()是一个特别耗费资源的操作,所以为了避免资源浪费,需要及时的调用cancelDiscovery()来释放资源。比如在进行设备连接之前,一定要先调用cancelDiscovery()

4.连接设备

蓝牙设备的连接和网络连接的模型十分相似,都是Client-Server模式,都通过一个socket来进行数据传输。作为一个Android设备,存在以下三种情况:

1.只作为Client端发起连接
2.只作为Server端等待别人发起建立连接的请求
3.同时作为Client和Server

因为我们这篇文章主要为介绍连接热敏打印机的做铺垫,所以这里我们只讲Android设备作为Client建立连接的情况。因为打印机也不可能主动跟Android设备建立连接,所以打印机必然是作为Server端被连接。

4.1 作为Client连接

  1. 首先需要获取一个BluetoothDevice对象。获取方式如前文介绍的,通过调用startDiscovery()并监听广播获得,也可以通过查询已配对的设备获得。
  2. 通过BluetoothDevice.createInsecureRfcommSocketToServiceRecord(UUID)得到BluetoothSocket对象。
  3. 通过BluetoothSocket.connect()建立连接
  4. 异常处理以及连接关闭
private class ConnectThread extends Thread {
    private final BluetoothSocket mmSocket;
    private final BluetoothDevice mmDevice;

    public ConnectThread(BluetoothDevice device) {
        BluetoothSocket tmp = null;
        mmDevice = device;
        try {
            // 通过 BluetoothDevice 获得 BluetoothSocket 对象
            tmp = device.createRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
        } catch (IOException e) { }
            mmSocket = tmp;
        }

    @Override
    public void run() {
        // 建立连接前记得取消设备发现
        mBluetoothAdapter.cancelDiscovery();
        try {
            // 耗时操作,所以必须在主线程之外进行
            mmSocket.connect();
        } catch (IOException connectException) {
            //处理连接建立失败的异常
            try {
                mmSocket.close();
            } catch (IOException closeException) { }
            return;
        }
        doSomething(mmSocket);
    }

    //关闭一个正在进行的连接
    public void cancel() {
        try {
            mmSocket.close();
        } catch (IOException e) { }
    }
}

Client发起连接时传入的UUID必须要和Server端设置的一样,都在就会报错。

如是像我们连接热敏打印机的这种情况,因为一些常见的蓝牙服务协议已有约定的UUID,比如我们连接热敏打印机是基于SPP串口通信协议,其对应的UUID是"00001101-0000-1000-8000-00805F9B34FB"。

5.数据传输

经过前面4步的操作,两个蓝牙设备已连接,准备就绪,现在就是利用Socket获得InputStream输入流和OutputStream输出流来进行数据得收发。

由于我们是与热敏打印机连接,所以我们只需要给打印机发送我们需要打印得内容。

private class ConnectedThread extends Thread {
    private final BluetoothSocket mmSocket;
    private final InputStream mmInStream;
    private final OutputStream mmOutStream;

    public ConnectedThread(BluetoothSocket socket) {
        mmSocket = socket;
        InputStream tmpIn = null;
        OutputStream tmpOut = null;
        //通过 socket 得到 InputStream 和 OutputStream
        try {
            tmpIn = socket.getInputStream();
            tmpOut = socket.getOutputStream();
        } catch (IOException e) { }

        mmInStream = tmpIn;
        mmOutStream = tmpOut;
    }

    public void run() {
        byte[] buffer = new byte[1024]; // buffer store for the stream
        int bytes; // bytes returned from read()

        //不断的从 InputStream 取数据
        while (true) {
            try {
                bytes = mmInStream.read(buffer);
                mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer).sendToTarget();
            } catch (IOException e) {
                break;
            }
        }
    }

    //向 Server 写入数据
    public void write(byte[] bytes) {
        try {
            mmOutStream.write(bytes);
        } catch (IOException e) { }
    }

    public void cancel() {
        try {
            mmSocket.close();
        } catch (IOException e) { }
    }
}

 

二、wifi云打印机连接

wifi打印机的连接方式跟蓝牙连接方式一样,都是Client-Server模式,通过一个socket来进行数据传输。由于手里还没有传统的wifi打印机,所以无法验证。但是公司有一台wifi云打印机,通过云服务器连接,调用WebAPI来连接发送数据实现打印。

我们先看一下效果图:

2.1 添加设备

先关注佳博科技的微信公众号,登录佳博云平台http://cloud.poscom/,注册云平台账号,获取API集成所需的商户编号和API密钥。下面是网页端的云平台界面:

我们也可以直接在云平台的终端管理里面去添加终端设备,这里我们就调用API的方式在我们自己的APP中向云服务添加一台我们的打印终端。详细接口文档可以参考:http://cloud.poscom/index.php?catid=18

@POST
@FormUrlEncoded
Observable<CommonResponse> addDevice(@Url String url,
                                     @Field("reqTime") String reqTime,
                                     @Field("securityCode") String securityCode,
                                     @Field("memberCode") String memberCode,
                                     @Field("deviceID") String deviceID,
                                     @Field("devName") String devName);
public Observable<CommonResponse> addDevice(String deviceID, String devName) {
    Retrofit retrofit = RetrofitUtils.getGsonRetrofit();
    String url = "http://api.poscom/apisc/adddev";
    String memberCode = "商户编号";
    String reqTime = String.valueOf(System.currentTimeMillis());
    String apiKey = "API key";
    String securityCode = Md5Utils.md5(memberCode + reqTime + apiKey + deviceID);
    return retrofit.create(IWifi.class).addDevice(url, reqTime, securityCode, memberCode, deviceID, devName);
}

注意:securityCode安全校验码,是用API密钥和规定的参数进行MD5运算的结果,注意顺序不能乱。另外,如果云服务器检测到该设备ID已存在,将提示设备已存在,需要先删除再添加。

2.2 删除打印机

@POST
@FormUrlEncoded
Observable<CommonResponse> deleteDevice(@Url String url,
                                        @Field("reqTime") String reqTime,
                                        @Field("securityCode") String securityCode,
                                        @Field("memberCode") String memberCode,
                                        @Field("deviceID") String deviceID);
public Observable<CommonResponse> deleteDevice(String deviceID) {
    Retrofit retrofit = RetrofitUtils.getGsonRetrofit();
    String url = "http://api.poscom/apisc/deldev";
    String memberCode = "商户编号";
    String reqTime = String.valueOf(System.currentTimeMillis());
    String apiKey = "API key";
    String securityCode = Md5Utils.md5(memberCode + reqTime + apiKey + deviceID);
    return retrofit.create(IWifi.class).deleteDevice(url, reqTime, securityCode, memberCode, deviceID);
}

注意:securityCode安全校验码的MD5运算顺序。

2.3 查询打印机列表

@POST
@FormUrlEncoded
Observable<ListDeviceResponse> getListDevices(@Url String url,
                                              @Field("reqTime") String reqTime,
                                              @Field("memberCode") String memberCode,
                                              @Field("securityCode") String securityCode);
public Observable<ListDeviceResponse> getListDevices() {
    Retrofit retrofit = RetrofitUtils.getGsonRetrofit();
    String url = "http://api.poscom/apisc/listDevice";
    String memberCode = "商户编号";
    String reqTime = String.valueOf(System.currentTimeMillis());
    String apiKey = "API key";
    String securityCode = Md5Utils.md5(memberCode + reqTime + apiKey);
    return retrofit.create(IWifi.class).getListDevices(url, reqTime, memberCode, securityCode);
}

注意:securityCode安全校验码的MD5运算顺序。

2.4 发送数据到打印机

@POST
@FormUrlEncoded
Observable<CommonResponse> sendMsg(@Url String url,
                                   @Field("reqTime") String reqTime,
                                   @Field("securityCode") String securityCode,
                                   @Field("memberCode") String memberCode,
                                   @Field("deviceID") String deviceID,
                                   @Field("mode") String mode,//model 2-3,2自由格式打印,推荐,3十六进制命字符串打印
                                   @Field("msgDetail") String msgDetail);
public Observable<CommonResponse> sendMsg(String deviceID) {
    Retrofit retrofit = RetrofitUtils.getGsonRetrofit();
    String url = "http://api.poscom/apisc/sendMsg";
    String memberCode = "商户编号";
    String reqTime = String.valueOf(System.currentTimeMillis());
    String apiKey = "API key";
    String securityCode = Md5Utils.md5(memberCode + deviceID + reqTime + apiKey);
    String mode = "2";
    String msgDetail = "<gpLogo/><gpWord Align=1 Bold=1 Wsize=2 Hsize=2 Reverse=0 Underline=0>发货单</gpWord>\n" +
                "<gpBarCode Align=1 Type=7 Width=2 Height=80 Position=0>201811080001</gpBarCode>\n" +
                "<gpWord Align=0 Bold=0 Wsize=0 Hsize=0 Reverse=0 Underline=0>订单编号:201811080001</gpWord>\n" +
                "<gpWord Align=0 Bold=0 Wsize=0 Hsize=0 Reverse=0 Underline=0>买家姓名:张三</gpWord>\n" +
                "<gpWord Align=0 Bold=0 Wsize=0 Hsize=0 Reverse=0 Underline=0>买家手机:18666666666</gpWord>\n" +
                "<gpWord Align=0 Bold=1 Wsize=0 Hsize=1 Reverse=0 Underline=0>买家留言:发顺丰,尽快发货,谢谢</gpWord>\n" +
                "<gpWord Align=0 Bold=1 Wsize=0 Hsize=1 Reverse=0 Underline=0>卖家备注:发顺丰,优先处理</gpWord>\n" +
                "<gpWord Align=0 Bold=1 Wsize=0 Hsize=1 Reverse=0 Underline=0>买就送信息:送U盘</gpWord>\n" +
                "<gpWord Align=0 Bold=0 Wsize=0 Hsize=0 Reverse=0 Underline=0>--------------------------------</gpWord>\n" +
                "<gpTR4 Type=0><td>宝贝名称</td><td>单价</td><td>数量</td><td>价格</td></gpTR4>\n" +
                "<gpTR4 Type=0><td>佳博GP-CH421D云打印机</td><td>1180</td><td>1</td><td>1180</td></gpTR4>\n" +
                "<gpTR4 Type=0><td>佳博GP-5890XIII云打印机</td><td>480</td><td>1</td><td>480</td></gpTR4>\n" +
                "<gpTR4 Type=0><td>佳博G3-350V云打印机</td><td>980</td><td>1</td><td>980</td></gpTR4>\n" +
                "<gpTR4 Type=0><td>100x150热敏标签纸300</td><td>36</td><td>10</td><td>360</td></gpTR4>\n" +
                "<gpTR4 Type=0><td>58毫米热敏卷纸100米</td><td>48</td><td>5</td><td>240</td></gpTR4>\n" +
                "<gpTR4 Type=0><td>80毫米热敏卷纸100米</td><td>42</td><td>10</td><td>420</td></gpTR4>\n" +
                "<gpWord Align=0 Bold=0 Wsize=0 Hsize=0 Reverse=0 Underline=0>-------------------------------- </gpWord>\n" +
                "<gpWord Align=2 Bold=0 Wsize=0 Hsize=0 Reverse=0 Underline=0>合计:3660元</gpWord>\n" +
                "<gpWord Align=2 Bold=0 Wsize=0 Hsize=0 Reverse=0 Underline=0>优惠:-198元</gpWord>\n" +
                "<gpWord Align=2 Bold=0 Wsize=0 Hsize=0 Reverse=0 Underline=0>邮费:  30元</gpWord>\n" +
                "<gpWord Align=2 Bold=1 Wsize=1 Hsize=1 Reverse=0 Underline=0>实收:3492元 </gpWord>\n" +
                "<gpCut/>\n" +
                "<gpWord Align=1 Bold=1 Wsize=1 Hsize=1 Reverse=0 Underline=0>扫码关注佳博</gpWord>\n" +
                "<gpQRCode Align=1 Size=9 Error=M>http://weixin.qq/r/kHV3b67EXPMjreoM9yCC</gpQRCode>\n" +
                "<gpCut/>";
    return retrofit.create(IWifi.class).sendMsg(url, reqTime, securityCode, memberCode, deviceID, mode, msgDetail);
}

佳博票据云打印格式详情可以参考http://cloud.poscom/index.php?id=152。

注意:securityCode安全校验码的MD5运算顺序不能乱。mode打印信息的类型,mode为2是自由格式打印,如上文格式,mode为3是十六进制命令集或十六进制字符串打印。

最后附上打印出来的效果图:

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

本文标签: 热敏蓝牙打印机打印androidwifi