众里寻他千百度,蓦然回首,那人却在灯火阑珊处
一、前言
在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-permission android:name="android.permission.USB_PERMISSION" />
<uses-feature android:name="android.hardware.usb.host" />
<meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
<meta-data android: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 UsbManager
mPermissionIntent = 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 fun setUsbDevices() {
// 列出所有的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 fun onReceive(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? = null
private var usbInterface: UsbInterface? = null
fun connectUsbPrinter(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
*/
fun write(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打印命令如下:
初始化打印机指令
//初始化打印机
public static byte[] init_printer() {
byte[] result = new byte[2];
result[0] = ESC;
result[1] = 0x40;
return result;
}
打印位置设置为居左对齐指令
/**
* 居左
*/
public static byte[] alignLeft() {
byte[] result = new byte[3];
result[0] = ESC;
result[1] = 97;
result[2] = 0;
return result;
}
打印位置设置为居中对齐指令
/**
* 居中对齐
*/
public static byte[] alignCenter() {
byte[] result = new byte[3];
result[0] = ESC;
result[1] = 97;
result[2] = 1;
return result;
}
打印位置设置居右对齐指令
/**
* 居右
*/
public static byte[] alignRight() {
byte[] result = new byte[3];
result[0] = ESC;
result[1] = 97;
result[2] = 2;
return result;
}
打印结束切刀指令
//切刀
public static byte[] 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 ,则为读数据
// */
fun printText(msg: String) {
try {
write(msg.toByteArray(charset("gbk")))
} catch (e: UnsupportedEncodingException) {
e.printStackTrace()
}
}
打印图片,条码,二维码,可以将图片条码二维码转化为bitmap,然后再打印
//光栅位图打印
public static byte[] 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:广播接收如下:
- 先注册扫码广播
<receiver android:name=".ScanGunReceiver">
<intent-filter>
<!-- 这里的 "SCAN_ACTION" 是扫码枪触发的action,需要替换为实际的值 -->
<action android:name="SCAN_ACTION" />
</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
中接收
- Activity中onKeyDown:中解析每一个keyCode对应的数字值
override fun onKeyDown(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
fun doNotSwitchViewPagerByKey(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转换为字符
*/
fun keyCodeToChar(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 fun acceptKey(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,打印机,扫码枪支付盒子,键盘鼠标等,接入的简单开发。当然涉及到的蓝牙,蓝牙打印机,分屏这些会在后面的文章中进行介绍。