Android串口,USB,打印机,扫码枪,支付盒子,键盘,鼠标,U盘等开发使用一网打尽
❝众里寻他千百度,蓦然回首,那人却在灯火阑珊处
❞
一、前言
在 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 setterspublic InputStream getInputStream() {return mFileInputStream;}public OutputStream getOutputStream() {return mFileOutputStream;}// JNIprivate 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.actionif (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 = usbDeviceif (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? = nullfunconnectUsbPrinter(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:广播接收如下:」
<receiverandroid:><intent-filter><!-- 这里的 "SCAN_ACTION" 是扫码枪触发的action,需要替换为实际的值 --><actionandroid: /></intent-filter></receiver>
import android.content.BroadcastReceiver;import android.content.Context;import android.content.Intent;public class ScanGunReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {// 获取扫码内容,这里的 "SCAN_RESULT" 是扫码枪提供的action,具体可能不同String scanContent = intent.getStringExtra("SCAN_RESULT");// 处理扫码内容if (scanContent != null) {// 扫码内容非空,执行相关逻辑}}}
「方式 2:在 Activity 的onKeyDown方法中监听,或者在Dialog.setOnKeyListener里面onKey中接收」
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)}
object KeyUtils {//控制按键 左右 tab 键 不切换 viewpagefundoNotSwitchViewPagerByKey(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 -> ""}}}
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 = falseif (!TextUtils.isEmpty(stringBuilder.toString())) {block?.invoke(stringBuilder.toString())}stringBuilder.delete(0, stringBuilder.length)isDeleteStringBuilder = true}}}
❝需要注意的是,扫码枪,支付盒子,键盘都是输入设备,要避免 UI 视图上面 控件焦点设置为 false, 同时界面不能有 EditText 控件,否则会将扫到的内容自动填入 EditText 控件里面去。
❞
六、总结
本文重点介绍了 Android 智能嵌入式设备,接入串口,USB, 打印机,扫码枪支付盒子,键盘鼠标等,接入的简单开发。当然涉及到的蓝牙,蓝牙打印机,分屏这些会在后面的文章中进行介绍。