一、前言
最近需求做扫码支付,但是扫码的扫码头是串口的,需要进行开发才能调起扫码头扫码,头疼了一段时间,在度娘上搜索了一波,做了下总结,也写了一个demo,顺便写一篇博文记录一下。
二、串口管理类
**
* 串口管理类
* <p>
* 触发扫码:7E 00 08 01 00 02 01 AB CD
* 停止扫码:7E 00 08 01 00 02 00 AB CD
* <p>
* <p>
* 一次读码延时(默认5秒)
* 10秒:7E 00 08 01 00 06 64 AB CD
* 15秒:7E 00 08 01 00 06 96 AB CD
* 20秒:7E 00 08 01 00 06 C8 AB CD
* 无限长:7E 00 08 01 00 06 00 AB CD
* 保存:7E 00 09 01 00 00 00 AB CD (如果不保存,断电后不能生效)
*
* @author Freak
* @date 2019/8/12.
*/
public class SerialPortManager {
private static final String TAG = "SerialPortManager";
private SerialReadThread mReadThread;
private OutputStream mOutputStream;
private HandlerThread mWriteThread;
private Scheduler mSendScheduler;
private static class InstanceHolder {
public static SerialPortManager sManager = new SerialPortManager();
}
public static SerialPortManager instance() {
return InstanceHolder.sManager;
}
private SerialPort mSerialPort;
private SerialPortManager() {
}
/**
* 打开串口
*
* @param device
* @return
*/
public SerialPort open(Device device) {
return open(device.getPath(), device.getBaudrate());
}
/**
* 打开串口
*
* @param devicePath
* @param baudrateString
* @return
*/
public SerialPort open(String devicePath, String baudrateString) {
if (mSerialPort != null) {
close();
}
try {
File device = new File(devicePath);
int baurate = Integer.parseInt(baudrateString);
mSerialPort = new SerialPort(device, baurate, 0);
mReadThread = new SerialReadThread(mSerialPort.getInputStream());
mReadThread.start();
mOutputStream = mSerialPort.getOutputStream();
mWriteThread = new HandlerThread("write-thread");
mWriteThread.start();
mSendScheduler = AndroidSchedulers.from(mWriteThread.getLooper());
return mSerialPort;
} catch (Throwable tr) {
LogUtil.e("打开串口失败", tr);
close();
return null;
}
}
/**
* 关闭串口
*/
public void close() {
if (mReadThread != null) {
mReadThread.close();
}
if (mOutputStream != null) {
try {
mOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (mWriteThread != null) {
mWriteThread.quit();
}
if (mSerialPort != null) {
mSerialPort.close();
mSerialPort = null;
}
}
/**
* 发送数据
*
* @param datas
* @return
*/
private void sendData(byte[] datas) throws Exception {
mOutputStream.write(datas);
}
/**
* (rx包裹)发送数据
*
* @param datas
* @return
*/
private Observable<Object> rxSendData(final byte[] datas) {
return Observable.create(new ObservableOnSubscribe<Object>() {
@Override
public void subscribe(ObservableEmitter<Object> emitter) throws Exception {
try {
sendData(datas);
emitter.onNext(new Object());
} catch (Exception e) {
LogUtil.e("发送:" + ByteUtil.bytes2HexStr(datas) + " 失败", e);
if (!emitter.isDisposed()) {
emitter.onError(e);
return;
}
}
emitter.onComplete();
}
});
}
/**
* 发送命令包
*/
@SuppressLint("CheckResult")
public void sendCommand(final String command) {
// TODO: 2018/3/22
LogUtil.i("发送命令:" + command);
byte[] bytes = ByteUtil.hexStr2bytes(command);
rxSendData(bytes).subscribeOn(mSendScheduler).subscribe(new Consumer<Object>() {
@Override
public void accept(Object o) throws Exception {
LogManager.instance().post(new SendMessage(command));
}
});
}
}
二、读写线程类
/**
* 串口读线程
*
* @author Freak
* @date 2019/8/12.
*/
public class SerialReadThread extends Thread {
private static final String TAG = "SerialReadThread";
private BufferedInputStream mInputStream;
public SerialReadThread(InputStream is) {
mInputStream = new BufferedInputStream(is);
}
@Override
public void run() {
byte[] received = new byte[1024];
int size;
LogUtil.e("开始读线程");
while (true) {
if (Thread.currentThread().isInterrupted()) {
break;
}
try {
int available = mInputStream.available();
if (available > 0) {
size = mInputStream.read(received);
if (size > 0) {
onDataReceive(received, size);
}
} else {
// 暂停一点时间,免得一直循环造成CPU占用率过高
SystemClock.sleep(1);
}
} catch (IOException e) {
LogUtil.e("读取数据失败", e);
}
//Thread.yield();
}
LogUtil.e("结束读进程");
}
/**
* 处理获取到的数据
*
* @param received
* @param size
*/
private void onDataReceive(byte[] received, int size) {
// TODO: 2018/3/22 解决粘包、分包等
String hexStr = ByteUtil.bytes2HexStr(received, 0, size);
LogManager.instance().post(new ReceiveMessage(hexStr));
LogUtil.e("扫码结果--》"+ hexStr);
}
/**
* 停止读线程
*/
public void close() {
try {
mInputStream.close();
} catch (IOException e) {
LogUtil.e("异常", e);
} finally {
super.interrupt();
}
}
}
三、串口读写字节数据处理类
/**
* @author Freak
* @date 2019/8/12.
*/
public class ByteUtil {
//public static void main(String[] args) {
// byte[] bytes = {
// (byte) 0xab, 0x01, 0x11
// };
// String hexStr = bytes2HexStr(bytes);
// System.out.println(hexStr);
// System.out.println(hexStr2decimal(hexStr));
// System.out.println(decimal2fitHex(570));
// String adc = "abc";
// System.out.println(str2HexString(adc));
// System.out.println(bytes2HexStr(adc.getBytes()));
//}
/**
* 字节数组转换成对应的16进制表示的字符串
*
* @param src
* @return
*/
public static String bytes2HexStr(byte[] src) {
StringBuilder builder = new StringBuilder();
if (src == null || src.length <= 0) {
return "";
}
char[] buffer = new char[2];
for (int i = 0; i < src.length; i++) {
buffer[0] = Character.forDigit((src[i] >>> 4) & 0x0F, 16);
buffer[1] = Character.forDigit(src[i] & 0x0F, 16);
builder.append(buffer);
}
return builder.toString().toUpperCase();
}
/**
* 十六进制字节数组转字符串
*
* @param src 目标数组
* @param dec 起始位置
* @param length 长度
* @return
*/
public static String bytes2HexStr(byte[] src, int dec, int length) {
byte[] temp = new byte[length];
System.arraycopy(src, dec, temp, 0, length);
return bytes2HexStr(temp);
}
/**
* 16进制字符串转10进制数字
*
* @param hex
* @return
*/
public static long hexStr2decimal(String hex) {
return Long.parseLong(hex, 16);
}
/**
* 把十进制数字转换成足位的十六进制字符串,并补全空位
*
* @param num
* @return
*/
public static String decimal2fitHex(long num) {
String hex = Long.toHexString(num).toUpperCase();
if (hex.length() % 2 != 0) {
return "0" + hex;
}
return hex.toUpperCase();
}
/**
* 把十进制数字转换成足位的十六进制字符串,并补全空位
*
* @param num
* @param strLength 字符串的长度
* @return
*/
public static String decimal2fitHex(long num, int strLength) {
String hexStr = decimal2fitHex(num);
StringBuilder stringBuilder = new StringBuilder(hexStr);
while (stringBuilder.length() < strLength) {
stringBuilder.insert(0, '0');
}
return stringBuilder.toString();
}
public static String fitDecimalStr(int dicimal, int strLength) {
StringBuilder builder = new StringBuilder(String.valueOf(dicimal));
while (builder.length() < strLength) {
builder.insert(0, "0");
}
return builder.toString();
}
/**
* 字符串转十六进制字符串
*
* @param str
* @return
*/
public static String str2HexString(String str) {
char[] chars = "0123456789ABCDEF".toCharArray();
StringBuilder sb = new StringBuilder();
byte[] bs = null;
try {
bs = str.getBytes("utf8");
} catch (Exception e) {
e.printStackTrace();
}
int bit;
for (int i = 0; i < bs.length; i++) {
bit = (bs[i] & 0x0f0) >> 4;
sb.append(chars[bit]);
bit = bs[i] & 0x0f;
sb.append(chars[bit]);
}
return sb.toString();
}
/**
* 把十六进制表示的字节数组字符串,转换成十六进制字节数组
*
* @param
* @return byte[]
*/
public static byte[] hexStr2bytes(String hex) {
int len = (hex.length() / 2);
byte[] result = new byte[len];
char[] achar = hex.toUpperCase().toCharArray();
for (int i = 0; i < len; i++) {
int pos = i * 2;
result[i] = (byte) (hexChar2byte(achar[pos]) << 4 | hexChar2byte(achar[pos + 1]));
}
return result;
}
/**
* 把16进制字符[0123456789abcde](含大小写)转成字节
*
* @param c
* @return
*/
private static int hexChar2byte(char c) {
switch (c) {
case '0':
return 0;
case '1':
return 1;
case '2':
return 2;
case '3':
return 3;
case '4':
return 4;
case '5':
return 5;
case '6':
return 6;
case '7':
return 7;
case '8':
return 8;
case '9':
return 9;
case 'a':
case 'A':
return 10;
case 'b':
case 'B':
return 11;
case 'c':
case 'C':
return 12;
case 'd':
case 'D':
return 13;
case 'e':
case 'E':
return 14;
case 'f':
case 'F':
return 15;
default:
return -1;
}
}
/**
* 十六进制字符串转换成十进制数字 耗性能,计算慢,调用次数少可以忽略不计
*
* @param hex
* @return
*/
public static String hexToString(String hex) {
StringBuilder sb = new StringBuilder();
for (int count = 0; count < hex.length() - 1; count += 2) {
//分离字符串,两位一组
String output = hex.substring(count, (count + 2));
//十六进制到十进制
int decimal = Integer.parseInt(output, 16);
//将小数点转换为字符
sb.append((char) decimal);
}
return sb.toString();
}
/**
* 十六进制字符串转换成十进制数字 相对上面的快些
*
* @param hex
* @return
*/
public static String hexToString1(String hex) {
StringBuilder sb = new StringBuilder();
char[] hexData = hex.toCharArray();
for (int count = 0; count < hexData.length - 1; count += 2) {
int firstDigit = Character.digit(hexData[count], 16);
int lastDigit = Character.digit(hexData[count + 1], 16);
int decimal = firstDigit * 16 + lastDigit;
sb.append((char) decimal);
}
return sb.toString();
}
/**
* 十六进制字符串转换成十进制数字
*
* @param str
* @return
*/
public static String hexToString3(final String str) {
return new String(new BigInteger(str, 16).toByteArray());
}
}
四、使用方法
4.1、初始化
- 打开串口
SerialPortManager.instance().open(mDevice)
- 发送串口命令
SerialPortManager.instance().sendCommand(text);
串口命令:
-
触发扫码:7E 00 08 01 00 02 01 AB CD 成功返回:02 00 00 01 00 33 31
-
停止扫码:7E 00 08 01 00 02 00 AB CD 成功返回:02 00 00 01 00 33 31
读码时长设置命令:
10秒:7E 00 08 01 00 06 64 AB CD
15秒:7E 00 08 01 00 06 96 AB CD
20秒:7E 00 08 01 00 06 C8 AB CD、
无限长:7E 00 08 01 00 06 00 AB CD
保存:7E 00 09 01 00 00 00 AB CD (如果不保存,断电后不能生效)
- 设置波特率为9600: 7E 00 08 02 00 2A 39 01 A7 EA 成功返回:02 00 00 02 39 01 C1 4C
- 将设置保存到内部Flash: 7E 00 09 01 00 00 00 DE C8 成功返回:02 00 00 01 00 33 31
- 查询波特率 :7E 00 07 01 00 2A 02 D8 0F 成功返回:02 00 00 02 39 01 C1 4C
- 恢复出厂设置 :7E 00 09 01 00 00 FF C0 38 成功返回:02 00 00 01 00 33 31
重点是串口扫码数据的读写和解析,串口扫码数据获取的数据,是hex编码的byte数组,要进行hex编码的校验和转换成十进制字符串;具体的可以看ByteUtil工具类。