稀土掘金技术社区

Android串口,USB,打印机,扫码枪,支付盒子,键盘,鼠标,U盘等开发使用一网打尽

Image
❝

众里寻他千百度,蓦然回首,那人却在灯火阑珊处

❞

一、前言

在 Android 智能设备开发过程中, 难免会遇到串口,USB, 扫码枪,支付盒子,打印机,键盘,鼠标等接入场景,其实这些很简单,只是大多数情况下,大家都在做手机端的 App 开发,接触这方面的很少。本文重点介绍下这些在 Android 系统下是怎么接入使用的。

二 、串口接入使用

1. 可以到官网下载串口包

里面含有 「libprt_serial_port.so」 这个库, 下载下来按照so使用方式接入就行了,还有 「SerialPort」 类: 如下:

public class SerialPort {
   private static final String TAG = "SerialPort";
   /*    * Do not remove or rename the field mFd: it is used by native method close();    */   private FileDescriptor mFd;   private FileInputStream mFileInputStream;   private FileOutputStream mFileOutputStream;
   public SerialPort(File device, int baudrate, int flags) throws SecurityException, IOException {
      mFd = open(device.getAbsolutePath(), baudrate, flags);      if (mFd == null) {         Log.e(TAG, "native open returns null");         throw new IOException();      }      mFileInputStream = new FileInputStream(mFd);      mFileOutputStream = new FileOutputStream(mFd);   }
   // Getters and setters   public InputStream getInputStream() {      return mFileInputStream;   }
   public OutputStream getOutputStream() {      return mFileOutputStream;   }
   // JNI   private native static FileDescriptor open(String path, int baudrate, int flags);   public native void close();   static {      System.loadLibrary("serial_port");   }}
2. 使用串口读取或者写入数据

需要配置串口路径和波特率,如下:路径为:/dev/ttyS4, 波特率为9600, 这 2 个参数是硬件厂商约定好的。

val serialPort = SerialPort(File("/dev/ttyS4"), 9600, 0);

读写数据需要从串口里面拿到 「输入输出流」:

inputStream = serialPort.inputStream  //outputStream = serialPort.outputStream

比如读取数据:

val length = inputStream!!.available()val bytes = new byte[length];inputStream.read(bytes);

到此,串口的使用基本就完成了。

至于串口读取后的数据怎么解析?
需要看串口数据的文档,不同硬件设备读取的不同内容出来格式不一样,按照厂商给的格式文档解析就完了,比如,串口连接的是秤,秤厂商硬件那边约定好的数据格式是怎样的,数据第 1 位什么意思,第 2 到第 X 位什么意思,xxx 位什么意思,这不同的厂商不同的,如果串口连接的不是秤,是其他硬件,约定的格式可能又不一样。

同理: 串口写数据,使用 outputStream流写入就行了, 写的具体内容,具体硬件厂商会有写入的文档,写入哪个数据是干什么用的,都在文档里面有。不同的写入功能,对应不同的写入内容命令。

三 、USB 接入使用

1、在 AndroidManifest 中添加 USB 使用配置

<uses-permissionandroid:name="android.permission.USB_PERMISSION" /><uses-featureandroid:name="android.hardware.usb.host" />
<meta-dataandroid:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" /><meta-dataandroid:name="android.hardware.usb.action.USB_DEVICE_DETACHED" />
2、防止 USB 插入拔出导致 Activity 生命周期发生变化需要在 Activity 下添加配置
android:configChanges="orientation|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation"
3、代码中具体使用:

比如接入 USB 打印机:

//拿到USB管理器mUsbManager = context.getSystemService(Context.USB_SERVICE) as UsbManagermPermissionIntent = PendingIntent.getBroadcast(context, 0, Intent(ACTION_USB_PERMISSION), 0)val filter = IntentFilter(USBPrinter.ACTION_USB_PERMISSION)filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED)//注册监听USB插入拔出监听广播context.registerReceiver(mUsbDeviceReceiver, filter)//开始检索出已经连接的USB设备setUsbDevices()

找到打印机设备,打印机设备的接口类型值固定式为 7(usbInterface.interfaceClass)

/** * 检索usb打印设备 */private funsetUsbDevices() {    // 列出所有的USB设备,并且都请求获取USB权限    mUsbManager?.deviceList?.let {        for (device in it.values) {            val usbInterface = device.getInterface(0)            if (usbInterface.interfaceClass == 7) {                //连接了多个USB打印机设备需要 判断vid,pid,(硬件厂商会给这个值的)来确定哪一个打印机                //检查该USB设备是否有权限                if (!mUsbManager!!.hasPermission(device)) {                    //申请该打印机USB权限                    mUsbManager!!.requestPermission(device, mPermissionIntent)                } else {                    connectUsbPrinter(device)                }                break            }        }    }}

USB 权限广播 action 收到后,就可以连接打印了

private val mUsbDeviceReceiver = object : BroadcastReceiver() {    override funonReceive(context: Context, intent: Intent) {        val action = intent.action        if (ACTION_USB_PERMISSION == action) {            synchronized(this) {                val usbDevice = intent.getParcelableExtra<UsbDevice>(UsbManager.EXTRA_DEVICE)                if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {                    //UsbDevice:在Android开发中用于表示连接到Android设备的USB设备                      mUsbDevice = usbDevice                    if (mUsbDevice != null) {                        connectUsbPrinter(mUsbDevice)                    }                } else {                    WLog.e(this, "Permission denied for device $usbDevice")                }            }        } else if (UsbManager.ACTION_USB_DEVICE_ATTACHED == action) {           //USB插入了               } else if (UsbManager.ACTION_USB_DEVICE_DETACHED == action) {           //USB拔出了            if (mUsbDevice != null) {                WLog.e(this, "Device closed")                if (mUsbDeviceConnection != null) {                    mUsbDeviceConnection!!.close()                }            }        }    }}

四、打印机的使用

Android 上面的打印机大多数是 USB 连接的打印机,还有蓝牙打印机。下面重点介绍 USB 打印机的使用: 在前面代码里找到 USB 打印设备后,我们需要拿到打印机的 UsbEndpoint,如下:

//UsbEndpoint:表示USB设备的单个端点。USB协议中,端点是用于发送和接收数据的逻辑private var printerEp: UsbEndpoint? = nullprivate var usbInterface: UsbInterface? = null
funconnectUsbPrinter(mUsbDevice: UsbDevice?) {    if (mUsbDevice != null) {        usbInterface = mUsbDevice.getInterface(0)        for (i in 0 until usbInterface!!.endpointCount) {            val ep = usbInterface!!.getEndpoint(i)            if (ep.type == UsbConstants.USB_ENDPOINT_XFER_BULK) {                if (ep.direction == UsbConstants.USB_DIR_OUT) {                    mUsbManager?.let {                        //与USB设备建立连接                        mUsbDeviceConnection = mUsbManager!!.openDevice(mUsbDevice)                        //拿到USB设备的端点                        printerEp = ep //拿到UsbEndpoint                    }                }            }        }    }}

开始打印:写入打印数据:

/** * usb写入 * * @param bytes */funwrite(bytes: ByteArray) {    if (mUsbDeviceConnection != null) {        try {            mUsbDeviceConnection!!.claimInterface(usbInterface, true)            //注意设定合理的超时值,以避免长时间阻塞            val b = mUsbDeviceConnection!!.bulkTransfer(printerEp, bytes, bytes.size, USBPrinter.TIME_OUT)            mUsbDeviceConnection!!.releaseInterface(usbInterface)        } catch (e: Exception) {            e.printStackTrace()
        }    }}

一般通用 USB 打印命令都是 ESC 打印命令如下:

初始化打印机指令

//初始化打印机publicstaticbyte[] init_printer() {    byte[] result = new byte[2];    result[0] = ESC;    result[1] = 0x40;    return result;}

打印位置设置为居左对齐指令

 /**     * 居左     */    publicstaticbyte[] alignLeft() {        byte[] result = new byte[3];        result[0] = ESC;        result[1] = 97;        result[2] = 0;        return result;    }

打印位置设置为居中对齐指令

    /**     * 居中对齐     */    publicstaticbyte[] alignCenter() {        byte[] result = new byte[3];        result[0] = ESC;        result[1] = 97;        result[2] = 1;        return result;    }

打印位置设置居右对齐指令

    /**     * 居右     */    publicstaticbyte[] alignRight() {        byte[] result = new byte[3];        result[0] = ESC;        result[1] = 97;        result[2] = 2;        return result;    }

打印结束切刀指令

    //切刀    publicstaticbyte[] cutter() {        byte[] box = new byte[6];        box[0] = 0x1B;        box[1] = 0x64;        box[2] = 0x01;        box[3] = 0x1d;        box[4] = 0x56;        box[5] = 0x31;//        byte[] data = new byte[]{0x1d, 0x56, 0x01};        return box;    }

打印文字

/** * 打印文字 * * @param msg *////**//     * 安卓9.0之前//     * 只要你传送的数据不大于16384 bytes,传送不会出问题,一旦数据大于16384 bytes,也可以传送,//     * 只是大于16384后面的数据就会丢失,获取到的数据永远都是前面的16384 bytes,//     * 所以,android USB Host 模式与HID使用bulkTransfer(endpoint,buffer,length,timeout)通讯时//     * buffer的长度不能超过16384。//     * <p>//     * controlTransfer( int requestType, int request , int value , int index , byte[] buffer , int length , int timeout)//     * 该方法通过0节点向此设备传输数据,传输的方向取决于请求的类别,如果requestType 为 USB_DIR_OUT 则为写数据 , USB _DIR_IN ,则为读数据//     */funprintText(msg: String) {    try {        write(msg.toByteArray(charset("gbk")))    } catch (e: UnsupportedEncodingException) {        e.printStackTrace()    }}

打印图片,条码,二维码,可以将图片条码二维码转化为 bitmap, 然后再打印

//光栅位图打印publicstaticbyte[] printBitmap(Bitmap bitmap) {    byte[] bytes1 = new byte[4];    bytes1[0] = GS;    bytes1[1] = 0x76;    bytes1[2] = 0x30;    bytes1[3] = 0x00;
    byte[] bytes2 = getBytesFromBitMap(bitmap);    return byteMerger(bytes1, bytes2);}

蓝牙打印机,放在下一篇文章介绍吧,一起介绍蓝牙,及蓝牙打印

五、扫码枪、支付盒子、键盘、鼠标使用

扫码枪,支付盒子,键盘,鼠标都是 USB 连接设备,只需要插入 Android 设备即可,前提是 Android 设备硬件含有 USB 接口,比如智能硬件 收银机,收银秤,车载插入 U 盘等

收银机 「扫码枪、支付盒子」 怎么扫码的?
大家知道,我们的支付码,条码,其实是一串数字内容的,扫到后是怎么解析的? 有两种方式的

「方式 1:广播接收如下:」
1. 「先注册扫码广播」
<receiverandroid:>    <intent-filter>        <!-- 这里的 "SCAN_ACTION" 是扫码枪触发的action,需要替换为实际的值 -->        <actionandroid: />    </intent-filter></receiver>
2. 「在广播接收器里面拿到扫码内容」
import android.content.BroadcastReceiver;import android.content.Context;import android.content.Intent;
public class ScanGunReceiver extends BroadcastReceiver {
    @Override    public void onReceive(Context context, Intent intent) {        // 获取扫码内容,这里的 "SCAN_RESULT" 是扫码枪提供的action,具体可能不同        String scanContent = intent.getStringExtra("SCAN_RESULT");
        // 处理扫码内容        if (scanContent != null) {            // 扫码内容非空,执行相关逻辑        }    }}
「方式 2:在 Activity 的onKeyDown方法中监听,或者在Dialog.setOnKeyListener里面onKey中接收」
1. Activity 中 onKeyDown:中解析每一个 keyCode 对应的数字值
override funonKeyDown(keyCode: Int, event: KeyEvent?): Boolean {    if (KeyUtils.doNotSwitchViewPagerByKey(keyCode)) {        //按了键盘上 左右键 tab 键        return true    }    scanHelpL.get().acceptKey(this, keyCode) {        viewModel.scanByBarcode(it)    }    return super.onKeyDown(keyCode, event)}
2. keyCode 值与具体对照值如下:
object KeyUtils {
    //控制按键 左右 tab 键 不切换 viewpage    fundoNotSwitchViewPagerByKey(keyCode: Int) = keyCode == KeyEvent.KEYCODE_DPAD_UP || keyCode == KeyEvent.KEYCODE_DPAD_DOWN || keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT || keyCode == KeyEvent.KEYCODE_TAB
    /**     * keyCode转换为字符     */    funkeyCodeToChar(code: Int, isShift: Boolean): String{        return when (code) {            KeyEvent.KEYCODE_SHIFT_LEFT -> ""            KeyEvent.KEYCODE_0 -> if (isShift) ")" else "0"            KeyEvent.KEYCODE_1 -> if (isShift) "!" else "1"            KeyEvent.KEYCODE_2 -> if (isShift) "@" else "2"            KeyEvent.KEYCODE_3 -> if (isShift) "#" else "3"            KeyEvent.KEYCODE_4 -> if (isShift) "$" else "4"            KeyEvent.KEYCODE_5 -> if (isShift) "%" else "5"            KeyEvent.KEYCODE_6 -> if (isShift) "^" else "6"            KeyEvent.KEYCODE_7 -> if (isShift) "&" else "7"            KeyEvent.KEYCODE_8 -> if (isShift) "*" else "8"            KeyEvent.KEYCODE_9 -> if (isShift) "(" else "9"            KeyEvent.KEYCODE_A -> if (isShift) "A" else "a"            KeyEvent.KEYCODE_B -> if (isShift) "B" else "b"            KeyEvent.KEYCODE_C -> if (isShift) "C" else "c"            KeyEvent.KEYCODE_D -> if (isShift) "D" else "d"            KeyEvent.KEYCODE_E -> if (isShift) "E" else "e"            KeyEvent.KEYCODE_F -> if (isShift) "F" else "f"            KeyEvent.KEYCODE_G -> if (isShift) "G" else "g"            KeyEvent.KEYCODE_H -> if (isShift) "H" else "h"            KeyEvent.KEYCODE_I -> if (isShift) "I" else "i"            KeyEvent.KEYCODE_J -> if (isShift) "J" else "j"            KeyEvent.KEYCODE_K -> if (isShift) "K" else "k"            KeyEvent.KEYCODE_L -> if (isShift) "L" else "l"            KeyEvent.KEYCODE_M -> if (isShift) "M" else "m"            KeyEvent.KEYCODE_N -> if (isShift) "N" else "n"            KeyEvent.KEYCODE_O -> if (isShift) "O" else "o"            KeyEvent.KEYCODE_P -> if (isShift) "P" else "p"            KeyEvent.KEYCODE_Q -> if (isShift) "Q" else "q"            KeyEvent.KEYCODE_R -> if (isShift) "R" else "r"            KeyEvent.KEYCODE_S -> if (isShift) "S" else "s"            KeyEvent.KEYCODE_T -> if (isShift) "T" else "t"            KeyEvent.KEYCODE_U -> if (isShift) "U" else "u"            KeyEvent.KEYCODE_V -> if (isShift) "V" else "v"            KeyEvent.KEYCODE_W -> if (isShift) "W" else "w"            KeyEvent.KEYCODE_X -> if (isShift) "X" else "x"            KeyEvent.KEYCODE_Y -> if (isShift) "Y" else "y"            KeyEvent.KEYCODE_Z -> if (isShift) "Z" else "z"            else -> ""        }    }}
3. 扫码枪和支付盒子扫完,最后一位是回车键:检测到回车键值时候,就可以将扫到的码的内容 提交出去处理支付等操作。如下:
private funacceptKey(keyCode: Int, block: (result: String) -> Unit) {    //监听扫码广播    if (keyCode != KeyEvent.KEYCODE_ENTER) {        if (isDeleteStringBuilder) {            val tmp: String = KeyUtils.keyCodeToChar(keyCode, hasShift)            stringBuilder.append(tmp)            hasShift = keyCode == KeyEvent.KEYCODE_SHIFT_LEFT        }    } else if (keyCode == KeyEvent.KEYCODE_ENTER) {        if (isDeleteStringBuilder) {            isDeleteStringBuilder = false            if (!TextUtils.isEmpty(stringBuilder.toString())) {                block?.invoke(stringBuilder.toString())            }            stringBuilder.delete(0, stringBuilder.length)            isDeleteStringBuilder = true        }    }}
❝

需要注意的是,扫码枪,支付盒子,键盘都是输入设备,要避免 UI 视图上面 控件焦点设置为 false, 同时界面不能有 EditText 控件,否则会将扫到的内容自动填入 EditText 控件里面去。

❞

六、总结

本文重点介绍了 Android 智能嵌入式设备,接入串口,USB, 打印机,扫码枪支付盒子,键盘鼠标等,接入的简单开发。当然涉及到的蓝牙,蓝牙打印机,分屏这些会在后面的文章中进行介绍。