25 changed files with 1539 additions and 665 deletions
-
10app/build.gradle
-
81app/src/main/java/com/baidu/idl/face/main/finance/utils/TestPopWindow.java
-
30app/src/main/java/com/ouxuan/oxface/device/Ox485.java
-
511app/src/main/java/com/ouxuan/oxface/device/OxGpio.java
-
22datalibrary/build.gradle
-
75datalibrary/src/main/java/com/example/datalibrary/db/DBManager.java
-
15facelibrary/build.gradle
-
28financelibrary/build.gradle
-
1lib-serialport/.gitignore
-
50lib-serialport/build.gradle
-
17lib-serialport/proguard-rules.pro
-
9lib-serialport/src/main/AndroidManifest.xml
-
9lib-serialport/src/main/cpp/CMakeLists.txt
-
167lib-serialport/src/main/cpp/SerialPort.c
-
30lib-serialport/src/main/cpp/SerialPort.h
-
48lib-serialport/src/main/java/com/kongqw/serialportlibrary/Device.java
-
54lib-serialport/src/main/java/com/kongqw/serialportlibrary/Driver.java
-
47lib-serialport/src/main/java/com/kongqw/serialportlibrary/SerialPort.java
-
66lib-serialport/src/main/java/com/kongqw/serialportlibrary/SerialPortFinder.java
-
187lib-serialport/src/main/java/com/kongqw/serialportlibrary/SerialPortManager.java
-
23lib-serialport/src/main/java/com/kongqw/serialportlibrary/listener/OnSerialPortDataListener.java
-
231lib-serialport/src/main/java/com/kongqw/serialportlibrary/thread/SerialPortReadThread.java
-
487lib-serialport/src/main/java/com/kongqw/serialportlibrary/utils/ByteUtils.java
-
3lib-serialport/src/main/res/values/strings.xml
-
3settings.gradle
@ -1,81 +0,0 @@ |
|||
package com.baidu.idl.face.main.finance.utils; |
|||
|
|||
import android.content.Context; |
|||
import android.util.Log; |
|||
import android.view.Gravity; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
import android.widget.PopupWindow; |
|||
|
|||
import com.baidu.idl.main.facesdk.financelibrary.R; |
|||
|
|||
public class TestPopWindow extends PopupWindow { |
|||
private String TAG = "TestPopWindow"; |
|||
private final Context gContext; |
|||
private View view; |
|||
|
|||
public TestPopWindow(Context context) { |
|||
this(context, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); |
|||
Log.d(TAG, "TestPopWindow: " + ViewGroup.LayoutParams.WRAP_CONTENT |
|||
+ "bbb:" + ViewGroup.LayoutParams.WRAP_CONTENT); |
|||
} |
|||
|
|||
public TestPopWindow(Context context, int width, int height) { |
|||
super(context); |
|||
this.gContext = context; |
|||
view = View.inflate(context, R.layout.layout_no_face_detected, null); |
|||
view.findViewById(R.id.retest_detectBtn).setOnClickListener(new View.OnClickListener() { |
|||
@Override |
|||
public void onClick(View view) { |
|||
mOnClickFinance.rester(true); |
|||
} |
|||
}); |
|||
view.findViewById(R.id.back_homeBtn).setOnClickListener(new View.OnClickListener() { |
|||
@Override |
|||
public void onClick(View view) { |
|||
mOnClickFinance.rester(false); |
|||
} |
|||
}); |
|||
|
|||
setContentView(view); |
|||
// 设置窗口的高和宽 |
|||
setWidth(width); |
|||
setHeight(height); |
|||
// 设置弹窗内科点击 |
|||
setTouchable(true); |
|||
setOutsideTouchable(true); |
|||
setFocusable(true); |
|||
// TODO去除背景 |
|||
setBackgroundDrawable(null); |
|||
|
|||
} |
|||
|
|||
/** |
|||
* 显示popupWindow |
|||
*/ |
|||
public void showPopupWindow(View parent) { |
|||
if (!this.isShowing()) { |
|||
// 以下拉方式显示popupwindow,调整位置使其不会完全遮挡预览界面 |
|||
this.showAtLocation(parent, Gravity.CENTER, 0, -50); |
|||
} else { |
|||
this.dismiss(); |
|||
} |
|||
} |
|||
|
|||
public void closePopupWindow() { |
|||
if (this.isShowing()) { |
|||
this.dismiss(); |
|||
} |
|||
} |
|||
|
|||
|
|||
public void setmOnClickFinance(OnClickFinance mOnClickFinance) { |
|||
this.mOnClickFinance = mOnClickFinance; |
|||
} |
|||
|
|||
public OnClickFinance mOnClickFinance; |
|||
|
|||
public interface OnClickFinance { |
|||
void rester(boolean isReTest); |
|||
} |
|||
} |
@ -1,511 +0,0 @@ |
|||
package com.ouxuan.oxface.device; |
|||
|
|||
import android.os.Handler; |
|||
import android.os.Looper; |
|||
import android.os.SystemClock; |
|||
import com.ouxuan.oxface.utils.LogManager; |
|||
|
|||
import java.io.BufferedReader; |
|||
import java.io.BufferedWriter; |
|||
import java.io.DataOutputStream; |
|||
import java.io.FileReader; |
|||
import java.io.FileWriter; |
|||
import java.io.IOException; |
|||
import java.util.concurrent.CompletableFuture; |
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
/** |
|||
* OxGpio GPIO控制和485通信模块 |
|||
* 基于原GpioUtils重写,并集成485串口通信功能 |
|||
* |
|||
* @author AI Assistant |
|||
* @version 1.0 |
|||
* @date 2024/09/11 |
|||
*/ |
|||
public class OxGpio { |
|||
|
|||
private static final String TAG = "OxGpio"; |
|||
|
|||
// GPIO引脚定义 - RK3399 |
|||
public static final int IO1_3399 = 1066; |
|||
public static final int IO2_3399 = 1067; |
|||
public static final int IO3_3399 = 1072; |
|||
public static final int IO4_3399 = 1071; |
|||
|
|||
// GPIO引脚定义 - RK3368 |
|||
public static final int IO1_3368 = 91; |
|||
public static final int IO2_3368 = 90; |
|||
public static final int IO3_3368 = 111; |
|||
public static final int IO4_3368 = 109; |
|||
|
|||
// 485通信配置 |
|||
private static final String SERIAL_PORT_PATH = "/dev/ttyS6"; |
|||
private static final int BAUD_RATE = 9600; |
|||
private static final int STOP_BITS = 1; |
|||
private static final String HEX_COMMAND_GET_PEOPLE_NUM = "0F030001000294E5"; |
|||
private static final long TIMEOUT_485_MILLIS = 10000; // 10秒超时 |
|||
|
|||
// 单例实例 |
|||
private static volatile OxGpio instance; |
|||
|
|||
// 485通信相关 |
|||
private SerialPort485 serialPort485; |
|||
private Handler mainHandler; |
|||
private boolean gateCamera485OxOn = false; |
|||
|
|||
// GPIO读取失败计数 |
|||
private static long mTime = 0; |
|||
private static int mFailTimes = 0; |
|||
|
|||
/** |
|||
* 485通信回调接口 |
|||
*/ |
|||
public interface PeopleNumCallback { |
|||
void onSuccess(int peopleNum); |
|||
void onError(String errorMessage); |
|||
} |
|||
|
|||
private OxGpio() { |
|||
mainHandler = new Handler(Looper.getMainLooper()); |
|||
// 初始化SerialPort485实例 |
|||
serialPort485 = SerialPort485.getInstance(); |
|||
} |
|||
|
|||
/** |
|||
* 获取OxGpio单例实例 |
|||
* @return OxGpio实例 |
|||
*/ |
|||
public static OxGpio getInstance() { |
|||
if (instance == null) { |
|||
synchronized (OxGpio.class) { |
|||
if (instance == null) { |
|||
instance = new OxGpio(); |
|||
} |
|||
} |
|||
} |
|||
return instance; |
|||
} |
|||
|
|||
/** |
|||
* 初始化OxGpio模块 |
|||
* @param context 上下文 |
|||
*/ |
|||
public void initialize(android.content.Context context) { |
|||
// 初始化SerialPort485 |
|||
serialPort485.initialize(context); |
|||
LogManager.logInfo(TAG, "OxGpio模块初始化完成"); |
|||
} |
|||
|
|||
// ==================== GPIO操作方法 ==================== |
|||
|
|||
/** |
|||
* 给export申请权限 |
|||
*/ |
|||
public static boolean upgradeRootPermissionForExport() { |
|||
LogManager.logInfo(TAG, "申请GPIO export权限"); |
|||
return upgradeRootPermission("/sys/class/gpio/export"); |
|||
} |
|||
|
|||
/** |
|||
* 配置一个gpio路径 |
|||
* @param gpio GPIO引脚号 |
|||
* @return 是否成功 |
|||
*/ |
|||
public static boolean exportGpio(int gpio) { |
|||
LogManager.logInfo(TAG, "导出GPIO: " + gpio); |
|||
return writeNode("/sys/class/gpio/export", String.valueOf(gpio)); |
|||
} |
|||
|
|||
/** |
|||
* 给获取io口的状态的路径申请权限,该方法需要在exportGpio后调用 |
|||
* @param gpio GPIO引脚号 |
|||
*/ |
|||
public static boolean upgradeRootPermissionForGpio(int gpio) { |
|||
LogManager.logInfo(TAG, "申请GPIO权限: " + gpio); |
|||
boolean directionResult = upgradeRootPermission("/sys/class/gpio/gpio" + gpio + "/direction"); |
|||
boolean valueResult = upgradeRootPermission("/sys/class/gpio/gpio" + gpio + "/value"); |
|||
return directionResult && valueResult; |
|||
} |
|||
|
|||
/** |
|||
* 设置io口为输入或输出 |
|||
* @param gpio GPIO引脚号 |
|||
* @param direction 0=输出(out), 1=输入(in) |
|||
* @return 是否成功 |
|||
*/ |
|||
public static boolean setGpioDirection(int gpio, int direction) { |
|||
String gpioDirection; |
|||
if (direction == 0) { |
|||
gpioDirection = "out"; |
|||
} else if (direction == 1) { |
|||
gpioDirection = "in"; |
|||
} else { |
|||
LogManager.logError(TAG, "无效的GPIO方向: " + direction); |
|||
return false; |
|||
} |
|||
|
|||
LogManager.logInfo(TAG, "设置GPIO方向 - 引脚: " + gpio + ", 方向: " + gpioDirection); |
|||
return writeNode("/sys/class/gpio/gpio" + gpio + "/direction", gpioDirection); |
|||
} |
|||
|
|||
/** |
|||
* 获取io口的状态为输出还是输入 |
|||
* @param gpio GPIO引脚号 |
|||
* @return "in" 或 "out" |
|||
*/ |
|||
public static String getGpioDirection(int gpio) { |
|||
String direction = readNode("/sys/class/gpio/gpio" + gpio + "/direction"); |
|||
LogManager.logDebug(TAG, "获取GPIO方向 - 引脚: " + gpio + ", 方向: " + direction); |
|||
return direction; |
|||
} |
|||
|
|||
/** |
|||
* 给输出io口写值,高电平或低电平 |
|||
* @param gpio GPIO引脚号 |
|||
* @param value "1"=高电平, "0"=低电平 |
|||
* @return 是否成功 |
|||
*/ |
|||
public static boolean writeGpioValue(int gpio, String value) { |
|||
LogManager.logInfo(TAG, "写入GPIO值 - 引脚: " + gpio + ", 值: " + value); |
|||
return writeNode("/sys/class/gpio/gpio" + gpio + "/value", value); |
|||
} |
|||
|
|||
/** |
|||
* 获取当前gpio是高还是低 |
|||
* @param gpio GPIO引脚号 |
|||
* @return "1"=高电平, "0"=低电平 |
|||
*/ |
|||
public static String getGpioValue(int gpio) { |
|||
String value = readNode("/sys/class/gpio/gpio" + gpio + "/value"); |
|||
LogManager.logDebug(TAG, "获取GPIO值 - 引脚: " + gpio + ", 值: " + value); |
|||
return value; |
|||
} |
|||
|
|||
// ==================== 485通信方法(转换自uniapp) ==================== |
|||
|
|||
/** |
|||
* 设置485模式开关 |
|||
* @param enabled 是否启用485模式 |
|||
*/ |
|||
public void setGateCamera485OxOn(boolean enabled) { |
|||
this.gateCamera485OxOn = enabled; |
|||
serialPort485.setGateCamera485OxOn(enabled); |
|||
LogManager.logInfo(TAG, "设置485模式开关: " + enabled); |
|||
} |
|||
|
|||
/** |
|||
* 获取485模式开关状态 |
|||
* @return true表示485模式已启用 |
|||
*/ |
|||
public boolean isGateCamera485OxOn() { |
|||
return gateCamera485OxOn; |
|||
} |
|||
|
|||
/** |
|||
* 通过485获取摄像头人数 |
|||
* 对应uniapp中的sendHex485ForPeopleNum方法 |
|||
* @param callback 结果回调 |
|||
*/ |
|||
public void sendHex485ForPeopleNum(PeopleNumCallback callback) { |
|||
LogManager.logInfo(TAG, "开始通过485获取摄像头人数"); |
|||
|
|||
if (!gateCamera485OxOn) { |
|||
String errorMsg = "485模式未开启"; |
|||
LogManager.logError(TAG, errorMsg); |
|||
if (callback != null) { |
|||
mainHandler.post(() -> callback.onError(errorMsg)); |
|||
} |
|||
return; |
|||
} |
|||
|
|||
// 使用SerialPort485进行通信 |
|||
serialPort485.sendHex485ForPeopleNum(new SerialPort485.SerialPort485Callback() { |
|||
@Override |
|||
public void onSuccess(int peopleNum) { |
|||
LogManager.logInfo(TAG, "485获取人数成功: " + peopleNum); |
|||
if (callback != null) { |
|||
mainHandler.post(() -> callback.onSuccess(peopleNum)); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onError(String errorMessage) { |
|||
LogManager.logError(TAG, "485获取人数失败: " + errorMessage); |
|||
if (callback != null) { |
|||
mainHandler.post(() -> callback.onError(errorMessage)); |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* 异步获取摄像头人数(使用CompletableFuture) |
|||
* @return CompletableFuture<Integer> 人数结果 |
|||
*/ |
|||
public CompletableFuture<Integer> sendHex485ForPeopleNumAsync() { |
|||
LogManager.logInfo(TAG, "异步获取摄像头人数"); |
|||
|
|||
CompletableFuture<Integer> future = new CompletableFuture<>(); |
|||
|
|||
sendHex485ForPeopleNum(new PeopleNumCallback() { |
|||
@Override |
|||
public void onSuccess(int peopleNum) { |
|||
future.complete(peopleNum); |
|||
} |
|||
|
|||
@Override |
|||
public void onError(String errorMessage) { |
|||
future.completeExceptionally(new RuntimeException(errorMessage)); |
|||
} |
|||
}); |
|||
|
|||
return future; |
|||
} |
|||
|
|||
/** |
|||
* 检查是否需要打开485串口 |
|||
*/ |
|||
public void checkIsNeedOpen485() { |
|||
LogManager.logInfo(TAG, "检查是否需要打开485串口"); |
|||
serialPort485.checkIsNeedOpen485(); |
|||
} |
|||
|
|||
/** |
|||
* 发送自定义485 HEX命令 |
|||
* @param hexCommand HEX命令字符串 |
|||
*/ |
|||
public void send485HexCommand(String hexCommand) { |
|||
LogManager.logInfo(TAG, "发送485 HEX命令: " + hexCommand); |
|||
serialPort485.sendHex(hexCommand); |
|||
} |
|||
|
|||
/** |
|||
* 获取485串口状态 |
|||
* @return 串口状态描述 |
|||
*/ |
|||
public String get485Status() { |
|||
return serialPort485.getSerialPortStatus(); |
|||
} |
|||
|
|||
/** |
|||
* 关闭485串口 |
|||
*/ |
|||
public void close485SerialPort() { |
|||
LogManager.logInfo(TAG, "关闭485串口"); |
|||
serialPort485.closeSerialPort(); |
|||
} |
|||
|
|||
// ==================== 内部辅助方法 ==================== |
|||
|
|||
/** |
|||
* 申请root权限 |
|||
* @param path 路径 |
|||
* @return 是否成功 |
|||
*/ |
|||
private static boolean upgradeRootPermission(String path) { |
|||
LogManager.logDebug(TAG, "申请root权限: " + path); |
|||
Process process = null; |
|||
DataOutputStream os = null; |
|||
try { |
|||
String cmd = "chmod 777 " + path; |
|||
process = Runtime.getRuntime().exec("su"); // 切换到root账号 |
|||
os = new DataOutputStream(process.getOutputStream()); |
|||
os.writeBytes(cmd + "\n"); |
|||
os.writeBytes("exit\n"); |
|||
os.flush(); |
|||
process.waitFor(); |
|||
} catch (Exception e) { |
|||
LogManager.logError(TAG, "申请root权限异常: " + path, e); |
|||
return false; |
|||
} finally { |
|||
try { |
|||
if (os != null) { |
|||
os.close(); |
|||
} |
|||
if (process != null) { |
|||
process.destroy(); |
|||
} |
|||
} catch (Exception e) { |
|||
LogManager.logError(TAG, "关闭进程异常", e); |
|||
} |
|||
} |
|||
|
|||
try { |
|||
boolean result = process != null && process.waitFor() == 0; |
|||
LogManager.logInfo(TAG, "申请root权限结果: " + result + " - " + path); |
|||
return result; |
|||
} catch (InterruptedException e) { |
|||
LogManager.logError(TAG, "等待进程中断", e); |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 写入节点 |
|||
* @param path 节点路径 |
|||
* @param value 写入值 |
|||
* @return 是否成功 |
|||
*/ |
|||
private static boolean writeNode(String path, String value) { |
|||
LogManager.logDebug(TAG, "写入节点 - 路径: " + path + ", 值: " + value); |
|||
|
|||
if (path == null || value == null) { |
|||
LogManager.logError(TAG, "写入节点参数错误 - 路径或值为空"); |
|||
return false; |
|||
} |
|||
|
|||
FileWriter fileWriter = null; |
|||
try { |
|||
fileWriter = new FileWriter(path); |
|||
fileWriter.write(value); |
|||
LogManager.logDebug(TAG, "节点写入成功: " + path); |
|||
return true; |
|||
} catch (Exception e) { |
|||
LogManager.logError(TAG, "写入节点异常 - 路径: " + path + ", 值: " + value, e); |
|||
return false; |
|||
} finally { |
|||
try { |
|||
if (fileWriter != null) { |
|||
fileWriter.close(); |
|||
} |
|||
} catch (IOException e) { |
|||
LogManager.logError(TAG, "关闭FileWriter异常", e); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 读取节点 |
|||
* @param path 节点路径 |
|||
* @return 读取的值 |
|||
*/ |
|||
private static String readNode(String path) { |
|||
LogManager.logDebug(TAG, "读取节点: " + path); |
|||
String result = ""; |
|||
|
|||
FileReader fread = null; |
|||
BufferedReader buffer = null; |
|||
|
|||
try { |
|||
fread = new FileReader(path); |
|||
buffer = new BufferedReader(fread); |
|||
String str; |
|||
while ((str = buffer.readLine()) != null) { |
|||
result = str; |
|||
break; |
|||
} |
|||
mFailTimes = 0; |
|||
LogManager.logDebug(TAG, "节点读取成功: " + path + " = " + result); |
|||
} catch (IOException e) { |
|||
LogManager.logError(TAG, "读取节点IO异常: " + path, e); |
|||
if (mTime == 0 || SystemClock.uptimeMillis() - mTime < 1000) { |
|||
mFailTimes++; |
|||
} |
|||
if (mFailTimes >= 3) { |
|||
LogManager.logWarning(TAG, "读取节点连续失败3次,可能需要检查权限: " + path); |
|||
} |
|||
} finally { |
|||
try { |
|||
if (buffer != null) { |
|||
buffer.close(); |
|||
} |
|||
if (fread != null) { |
|||
fread.close(); |
|||
} |
|||
} catch (IOException e) { |
|||
LogManager.logError(TAG, "关闭文件流异常", e); |
|||
} |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
// ==================== 便捷方法 ==================== |
|||
|
|||
/** |
|||
* 初始化GPIO引脚(包含导出、申请权限、设置方向) |
|||
* @param gpio GPIO引脚号 |
|||
* @param direction 0=输出, 1=输入 |
|||
* @return 是否成功 |
|||
*/ |
|||
public static boolean initializeGpio(int gpio, int direction) { |
|||
LogManager.logInfo(TAG, "初始化GPIO - 引脚: " + gpio + ", 方向: " + direction); |
|||
|
|||
// 1. 申请export权限 |
|||
if (!upgradeRootPermissionForExport()) { |
|||
LogManager.logError(TAG, "申请export权限失败"); |
|||
return false; |
|||
} |
|||
|
|||
// 2. 导出GPIO |
|||
if (!exportGpio(gpio)) { |
|||
LogManager.logError(TAG, "导出GPIO失败: " + gpio); |
|||
return false; |
|||
} |
|||
|
|||
// 3. 申请GPIO权限 |
|||
if (!upgradeRootPermissionForGpio(gpio)) { |
|||
LogManager.logError(TAG, "申请GPIO权限失败: " + gpio); |
|||
return false; |
|||
} |
|||
|
|||
// 4. 设置GPIO方向 |
|||
if (!setGpioDirection(gpio, direction)) { |
|||
LogManager.logError(TAG, "设置GPIO方向失败: " + gpio); |
|||
return false; |
|||
} |
|||
|
|||
LogManager.logInfo(TAG, "GPIO初始化成功: " + gpio); |
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* 设置GPIO输出高电平 |
|||
* @param gpio GPIO引脚号 |
|||
* @return 是否成功 |
|||
*/ |
|||
public static boolean setGpioHigh(int gpio) { |
|||
LogManager.logInfo(TAG, "设置GPIO高电平: " + gpio); |
|||
return writeGpioValue(gpio, "1"); |
|||
} |
|||
|
|||
/** |
|||
* 设置GPIO输出低电平 |
|||
* @param gpio GPIO引脚号 |
|||
* @return 是否成功 |
|||
*/ |
|||
public static boolean setGpioLow(int gpio) { |
|||
LogManager.logInfo(TAG, "设置GPIO低电平: " + gpio); |
|||
return writeGpioValue(gpio, "0"); |
|||
} |
|||
|
|||
/** |
|||
* 检查GPIO是否为高电平 |
|||
* @param gpio GPIO引脚号 |
|||
* @return true表示高电平 |
|||
*/ |
|||
public static boolean isGpioHigh(int gpio) { |
|||
String value = getGpioValue(gpio); |
|||
return "1".equals(value); |
|||
} |
|||
|
|||
/** |
|||
* 检查GPIO是否为低电平 |
|||
* @param gpio GPIO引脚号 |
|||
* @return true表示低电平 |
|||
*/ |
|||
public static boolean isGpioLow(int gpio) { |
|||
String value = getGpioValue(gpio); |
|||
return "0".equals(value); |
|||
} |
|||
|
|||
/** |
|||
* 释放资源 |
|||
*/ |
|||
public void release() { |
|||
LogManager.logInfo(TAG, "释放OxGpio资源"); |
|||
if (serialPort485 != null) { |
|||
serialPort485.release(); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1 @@ |
|||
/build |
@ -0,0 +1,50 @@ |
|||
apply plugin: 'com.android.library' |
|||
|
|||
android { |
|||
namespace 'com.kongqw.serialportlibrary' |
|||
compileSdk 35 |
|||
buildToolsVersion "35.0.0" |
|||
|
|||
defaultConfig { |
|||
minSdkVersion 21 |
|||
targetSdkVersion 35 |
|||
versionCode 1 |
|||
versionName "1.0" |
|||
|
|||
consumerProguardFiles "consumer-rules.pro" |
|||
|
|||
externalNativeBuild { |
|||
cmake { |
|||
cppFlags "" |
|||
abiFilters "armeabi-v7a", "arm64-v8a" |
|||
} |
|||
} |
|||
} |
|||
|
|||
externalNativeBuild { |
|||
cmake { |
|||
path "src/main/cpp/CMakeLists.txt" |
|||
version "3.31.6" |
|||
} |
|||
} |
|||
|
|||
buildTypes { |
|||
release { |
|||
minifyEnabled false |
|||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' |
|||
} |
|||
debug { |
|||
debuggable true |
|||
jniDebuggable true |
|||
} |
|||
} |
|||
|
|||
compileOptions { |
|||
sourceCompatibility JavaVersion.VERSION_1_8 |
|||
targetCompatibility JavaVersion.VERSION_1_8 |
|||
} |
|||
} |
|||
|
|||
dependencies { |
|||
implementation 'androidx.annotation:annotation:1.7.0' |
|||
} |
@ -0,0 +1,17 @@ |
|||
# Add project specific ProGuard rules here. |
|||
# By default, the flags in this file are appended to flags specified |
|||
# in D:\Android\sdk/tools/proguard/proguard-android.txt |
|||
# You can edit the include path and order by changing the proguardFiles |
|||
# directive in build.gradle. |
|||
# |
|||
# For more details, see |
|||
# http://developer.android.com/guide/developing/tools/proguard.html |
|||
|
|||
# Add any project specific keep options here: |
|||
|
|||
# If your project uses WebView with JS, uncomment the following |
|||
# and specify the fully qualified class name to the JavaScript interface |
|||
# class: |
|||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview { |
|||
# public *; |
|||
#} |
@ -0,0 +1,9 @@ |
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" |
|||
package="com.kongqw.serialportlibrary"> |
|||
|
|||
<application android:allowBackup="false" android:label="@string/app_name" |
|||
android:supportsRtl="true"> |
|||
|
|||
</application> |
|||
|
|||
</manifest> |
@ -0,0 +1,9 @@ |
|||
cmake_minimum_required(VERSION 3.4.1) |
|||
|
|||
add_library(SerialPort SHARED |
|||
SerialPort.c) |
|||
|
|||
# Include libraries needed for libserial_port lib |
|||
target_link_libraries(SerialPort |
|||
android |
|||
log) |
@ -0,0 +1,167 @@ |
|||
/* |
|||
* Copyright 2009-2011 Cedric Priscal |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0 |
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
|
|||
#include <termios.h> |
|||
#include <unistd.h> |
|||
#include <sys/types.h> |
|||
#include <sys/stat.h> |
|||
#include <fcntl.h> |
|||
#include <string.h> |
|||
#include <jni.h> |
|||
|
|||
#include "SerialPort.h" |
|||
|
|||
#include "android/log.h" |
|||
static const char *TAG="serial_port"; |
|||
#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO, TAG, fmt, ##args) |
|||
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##args) |
|||
#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##args) |
|||
|
|||
static speed_t getBaudrate(jint baudrate) |
|||
{ |
|||
switch(baudrate) { |
|||
case 0: return B0; |
|||
case 50: return B50; |
|||
case 75: return B75; |
|||
case 110: return B110; |
|||
case 134: return B134; |
|||
case 150: return B150; |
|||
case 200: return B200; |
|||
case 300: return B300; |
|||
case 600: return B600; |
|||
case 1200: return B1200; |
|||
case 1800: return B1800; |
|||
case 2400: return B2400; |
|||
case 4800: return B4800; |
|||
case 9600: return B9600; |
|||
case 19200: return B19200; |
|||
case 38400: return B38400; |
|||
case 57600: return B57600; |
|||
case 115200: return B115200; |
|||
case 230400: return B230400; |
|||
case 460800: return B460800; |
|||
case 500000: return B500000; |
|||
case 576000: return B576000; |
|||
case 921600: return B921600; |
|||
case 1000000: return B1000000; |
|||
case 1152000: return B1152000; |
|||
case 1500000: return B1500000; |
|||
case 2000000: return B2000000; |
|||
case 2500000: return B2500000; |
|||
case 3000000: return B3000000; |
|||
case 3500000: return B3500000; |
|||
case 4000000: return B4000000; |
|||
default: return -1; |
|||
} |
|||
} |
|||
|
|||
/* |
|||
* Class: android_serialport_SerialPort |
|||
* Method: open |
|||
* Signature: (Ljava/lang/String;II)Ljava/io/FileDescriptor; |
|||
*/ |
|||
JNIEXPORT jobject JNICALL Java_com_kongqw_serialportlibrary_SerialPort_open |
|||
(JNIEnv *env, jclass thiz, jstring path, jint baudrate, jint flags) |
|||
{ |
|||
int fd; |
|||
speed_t speed; |
|||
jobject mFileDescriptor; |
|||
|
|||
/* Check arguments */ |
|||
{ |
|||
speed = getBaudrate(baudrate); |
|||
if (speed == -1) { |
|||
/* TODO: throw an exception */ |
|||
LOGE("Invalid baudrate"); |
|||
return NULL; |
|||
} |
|||
} |
|||
|
|||
/* Opening device */ |
|||
{ |
|||
jboolean iscopy; |
|||
const char *path_utf = (*env)->GetStringUTFChars(env, path, &iscopy); |
|||
LOGD("Opening serial port %s with flags 0x%x", path_utf, O_RDWR | flags); |
|||
fd = open(path_utf, O_RDWR | flags); |
|||
LOGD("open() fd = %d", fd); |
|||
(*env)->ReleaseStringUTFChars(env, path, path_utf); |
|||
if (fd == -1) |
|||
{ |
|||
/* Throw an exception */ |
|||
LOGE("Cannot open port"); |
|||
/* TODO: throw an exception */ |
|||
return NULL; |
|||
} |
|||
} |
|||
|
|||
/* Configure device */ |
|||
{ |
|||
struct termios cfg; |
|||
LOGD("Configuring serial port"); |
|||
if (tcgetattr(fd, &cfg)) |
|||
{ |
|||
LOGE("tcgetattr() failed"); |
|||
close(fd); |
|||
/* TODO: throw an exception */ |
|||
return NULL; |
|||
} |
|||
|
|||
cfmakeraw(&cfg); |
|||
cfsetispeed(&cfg, speed); |
|||
cfsetospeed(&cfg, speed); |
|||
|
|||
if (tcsetattr(fd, TCSANOW, &cfg)) |
|||
{ |
|||
LOGE("tcsetattr() failed"); |
|||
close(fd); |
|||
/* TODO: throw an exception */ |
|||
return NULL; |
|||
} |
|||
} |
|||
|
|||
/* Create a corresponding file descriptor */ |
|||
{ |
|||
jclass cFileDescriptor = (*env)->FindClass(env, "java/io/FileDescriptor"); |
|||
jmethodID iFileDescriptor = (*env)->GetMethodID(env, cFileDescriptor, "<init>", "()V"); |
|||
jfieldID descriptorID = (*env)->GetFieldID(env, cFileDescriptor, "descriptor", "I"); |
|||
mFileDescriptor = (*env)->NewObject(env, cFileDescriptor, iFileDescriptor); |
|||
(*env)->SetIntField(env, mFileDescriptor, descriptorID, (jint)fd); |
|||
} |
|||
|
|||
return mFileDescriptor; |
|||
} |
|||
|
|||
/* |
|||
* Class: cedric_serial_SerialPort |
|||
* Method: close |
|||
* Signature: ()V |
|||
*/ |
|||
JNIEXPORT void JNICALL Java_com_kongqw_serialportlibrary_SerialPort_close |
|||
(JNIEnv *env, jobject thiz) |
|||
{ |
|||
jclass SerialPortClass = (*env)->GetObjectClass(env, thiz); |
|||
jclass FileDescriptorClass = (*env)->FindClass(env, "java/io/FileDescriptor"); |
|||
|
|||
jfieldID mFdID = (*env)->GetFieldID(env, SerialPortClass, "mFd", "Ljava/io/FileDescriptor;"); |
|||
jfieldID descriptorID = (*env)->GetFieldID(env, FileDescriptorClass, "descriptor", "I"); |
|||
|
|||
jobject mFd = (*env)->GetObjectField(env, thiz, mFdID); |
|||
jint descriptor = (*env)->GetIntField(env, mFd, descriptorID); |
|||
|
|||
LOGD("close(fd = %d)", descriptor); |
|||
close(descriptor); |
|||
} |
|||
|
@ -0,0 +1,30 @@ |
|||
/* DO NOT EDIT THIS FILE - it is machine generated */ |
|||
#include <jni.h> |
|||
/* Header for class android_serialport_api_SerialPort */ |
|||
|
|||
#ifndef _Included_qingwei_kong_serialportlibrary_SerialPort |
|||
#define _Included_qingwei_kong_serialportlibrary_SerialPort |
|||
#ifdef __cplusplus |
|||
extern "C" { |
|||
#endif |
|||
|
|||
/* |
|||
* Class: android_serialport_api_SerialPort |
|||
* Method: open |
|||
* Signature: (Ljava/lang/String;II)Ljava/io/FileDescriptor; |
|||
*/ |
|||
JNIEXPORT jobject JNICALL Java_com_kongqw_serialportlibrary_SerialPort_open |
|||
(JNIEnv *, jclass, jstring, jint, jint); |
|||
|
|||
/* |
|||
* Class: android_serialport_api_SerialPort |
|||
* Method: close |
|||
* Signature: ()V |
|||
*/ |
|||
JNIEXPORT void JNICALL Java_com_kongqw_serialportlibrary_SerialPort_close |
|||
(JNIEnv *, jobject); |
|||
|
|||
#ifdef __cplusplus |
|||
} |
|||
#endif |
|||
#endif |
@ -0,0 +1,48 @@ |
|||
package com.kongqw.serialportlibrary; |
|||
|
|||
import java.io.File; |
|||
import java.io.Serializable; |
|||
|
|||
/** |
|||
* Created by Kongqw on 2017/11/13. |
|||
* Device |
|||
*/ |
|||
|
|||
public class Device implements Serializable{ |
|||
|
|||
private static final String TAG = Device.class.getSimpleName(); |
|||
|
|||
private String name; |
|||
private String root; |
|||
private File file; |
|||
|
|||
public Device(String name, String root, File file) { |
|||
this.name = name; |
|||
this.root = root; |
|||
this.file = file; |
|||
} |
|||
|
|||
public String getName() { |
|||
return name; |
|||
} |
|||
|
|||
public void setName(String name) { |
|||
this.name = name; |
|||
} |
|||
|
|||
public String getRoot() { |
|||
return root; |
|||
} |
|||
|
|||
public void setRoot(String root) { |
|||
this.root = root; |
|||
} |
|||
|
|||
public File getFile() { |
|||
return file; |
|||
} |
|||
|
|||
public void setFile(File path) { |
|||
this.file = file; |
|||
} |
|||
} |
@ -0,0 +1,54 @@ |
|||
package com.kongqw.serialportlibrary; |
|||
|
|||
import android.util.Log; |
|||
|
|||
import java.io.File; |
|||
import java.util.ArrayList; |
|||
|
|||
/** |
|||
* Created by Kongqw on 2017/11/13. |
|||
* Driver |
|||
*/ |
|||
|
|||
public class Driver { |
|||
|
|||
private static final String TAG = Driver.class.getSimpleName(); |
|||
|
|||
private String mDriverName; |
|||
private String mDeviceRoot; |
|||
|
|||
public Driver(String name, String root) { |
|||
mDriverName = name; |
|||
mDeviceRoot = root; |
|||
} |
|||
|
|||
public ArrayList<File> getDevices() { |
|||
ArrayList<File> devices = new ArrayList<>(); |
|||
File dev = new File("/dev"); |
|||
|
|||
if (!dev.exists()) { |
|||
Log.i(TAG, "getDevices: " + dev.getAbsolutePath() + " 不存在"); |
|||
return devices; |
|||
} |
|||
if (!dev.canRead()) { |
|||
Log.i(TAG, "getDevices: " + dev.getAbsolutePath() + " 没有读取权限"); |
|||
return devices; |
|||
} |
|||
|
|||
File[] files = dev.listFiles(); |
|||
|
|||
int i; |
|||
for (i = 0; i < files.length; i++) { |
|||
if (files[i].getAbsolutePath().startsWith(mDeviceRoot)) { |
|||
Log.d(TAG, "Found new device: " + files[i]); |
|||
devices.add(files[i]); |
|||
} |
|||
} |
|||
return devices; |
|||
} |
|||
|
|||
public String getName() { |
|||
return mDriverName; |
|||
} |
|||
|
|||
} |
@ -0,0 +1,47 @@ |
|||
package com.kongqw.serialportlibrary; |
|||
|
|||
import java.io.File; |
|||
import java.io.FileDescriptor; |
|||
import java.io.IOException; |
|||
|
|||
public class SerialPort { |
|||
|
|||
static { |
|||
System.loadLibrary("SerialPort"); |
|||
} |
|||
|
|||
private static final String TAG = SerialPort.class.getSimpleName(); |
|||
|
|||
/** |
|||
* 文件设置最高权限 777 可读 可写 可执行 |
|||
* |
|||
* @param file 文件 |
|||
* @return 权限修改是否成功 |
|||
*/ |
|||
boolean chmod777(File file) { |
|||
if (null == file || !file.exists()) { |
|||
// 文件不存在 |
|||
return false; |
|||
} |
|||
try { |
|||
// 获取ROOT权限 |
|||
Process su = Runtime.getRuntime().exec("/system/bin/su"); |
|||
// 修改文件属性为 [可读 可写 可执行] |
|||
String cmd = "chmod 777 " + file.getAbsolutePath() + "\n" + "exit\n"; |
|||
su.getOutputStream().write(cmd.getBytes()); |
|||
if (0 == su.waitFor() && file.canRead() && file.canWrite() && file.canExecute()) { |
|||
return true; |
|||
} |
|||
} catch (IOException | InterruptedException e) { |
|||
// 没有ROOT权限 |
|||
e.printStackTrace(); |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
// 打开串口 |
|||
protected native FileDescriptor open(String path, int baudRate, int flags); |
|||
|
|||
// 关闭串口 |
|||
protected native void close(); |
|||
} |
@ -0,0 +1,66 @@ |
|||
package com.kongqw.serialportlibrary; |
|||
|
|||
import android.util.Log; |
|||
|
|||
import java.io.File; |
|||
import java.io.FileReader; |
|||
import java.io.IOException; |
|||
import java.io.LineNumberReader; |
|||
import java.util.ArrayList; |
|||
|
|||
public class SerialPortFinder { |
|||
|
|||
private static final String TAG = SerialPortFinder.class.getSimpleName(); |
|||
private static final String DRIVERS_PATH = "/proc/tty/drivers"; |
|||
private static final String SERIAL_FIELD = "serial"; |
|||
|
|||
public SerialPortFinder() { |
|||
File file = new File(DRIVERS_PATH); |
|||
boolean b = file.canRead(); |
|||
Log.i(TAG, "SerialPortFinder: file.canRead() = " + b); |
|||
} |
|||
|
|||
/** |
|||
* 获取 Drivers |
|||
* |
|||
* @return Drivers |
|||
* @throws IOException IOException |
|||
*/ |
|||
private ArrayList<Driver> getDrivers() throws IOException { |
|||
ArrayList<Driver> drivers = new ArrayList<>(); |
|||
LineNumberReader lineNumberReader = new LineNumberReader(new FileReader(DRIVERS_PATH)); |
|||
String readLine; |
|||
while ((readLine = lineNumberReader.readLine()) != null) { |
|||
String driverName = readLine.substring(0, 0x15).trim(); |
|||
String[] fields = readLine.split(" +"); |
|||
if ((fields.length >= 5) && (fields[fields.length - 1].equals(SERIAL_FIELD))) { |
|||
Log.d(TAG, "Found new driver " + driverName + " on " + fields[fields.length - 4]); |
|||
drivers.add(new Driver(driverName, fields[fields.length - 4])); |
|||
} |
|||
} |
|||
return drivers; |
|||
} |
|||
|
|||
/** |
|||
* 获取串口 |
|||
* |
|||
* @return 串口 |
|||
*/ |
|||
public ArrayList<Device> getDevices() { |
|||
ArrayList<Device> devices = new ArrayList<>(); |
|||
try { |
|||
ArrayList<Driver> drivers = getDrivers(); |
|||
for (Driver driver : drivers) { |
|||
String driverName = driver.getName(); |
|||
ArrayList<File> driverDevices = driver.getDevices(); |
|||
for (File file : driverDevices) { |
|||
String devicesName = file.getName(); |
|||
devices.add(new Device(devicesName, driverName, file)); |
|||
} |
|||
} |
|||
} catch (IOException e) { |
|||
e.printStackTrace(); |
|||
} |
|||
return devices; |
|||
} |
|||
} |
@ -0,0 +1,187 @@ |
|||
package com.kongqw.serialportlibrary; |
|||
|
|||
import android.os.Handler; |
|||
import android.os.HandlerThread; |
|||
import android.os.Message; |
|||
import android.util.Log; |
|||
|
|||
import com.kongqw.serialportlibrary.listener.OnSerialPortDataListener; |
|||
import com.kongqw.serialportlibrary.thread.SerialPortReadThread; |
|||
|
|||
import java.io.File; |
|||
import java.io.FileDescriptor; |
|||
import java.io.FileInputStream; |
|||
import java.io.FileOutputStream; |
|||
import java.io.IOException; |
|||
|
|||
/** |
|||
* Created by Kongqw on 2017/11/13. |
|||
* SerialPortManager |
|||
*/ |
|||
|
|||
public class SerialPortManager extends SerialPort { |
|||
private static final String TAG = SerialPortManager.class.getSimpleName(); |
|||
private FileInputStream mFileInputStream; |
|||
private FileOutputStream mFileOutputStream; |
|||
private FileDescriptor mFd; |
|||
private OnSerialPortDataListener mOnSerialPortDataListener; |
|||
|
|||
private HandlerThread mSendingHandlerThread; |
|||
private Handler mSendingHandler; |
|||
private SerialPortReadThread mSerialPortReadThread; |
|||
|
|||
/** |
|||
* 打开串口 |
|||
* |
|||
* @param device 串口设备 |
|||
* @param baudRate 波特率 |
|||
* @return 打开是否成功 |
|||
*/ |
|||
public boolean openSerialPort(File device, int baudRate) { |
|||
Log.i(TAG, "openSerialPort: " + String.format("打开串口 %s 波特率 %s", device.getPath(), baudRate)); |
|||
// 校验串口权限 |
|||
if (!device.canRead() || !device.canWrite()) { |
|||
boolean chmod777 = chmod777(device); |
|||
if (!chmod777) { |
|||
Log.i(TAG, "openSerialPort: 没有读写权限"); |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
try { |
|||
mFd = open(device.getAbsolutePath(), baudRate, 0); |
|||
mFileInputStream = new FileInputStream(mFd); |
|||
mFileOutputStream = new FileOutputStream(mFd); |
|||
Log.i(TAG, "openSerialPort: 串口已经打开 " + mFd); |
|||
return true; |
|||
} catch (Exception e) { |
|||
e.printStackTrace(); |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* 关闭串口 |
|||
*/ |
|||
public void closeSerialPort() { |
|||
if (null != mFd) { |
|||
close(); |
|||
mFd = null; |
|||
} |
|||
// 停止发送消息的线程 |
|||
stopSendThread(); |
|||
// 停止接收消息的线程 |
|||
stopReadThread(); |
|||
|
|||
if (null != mFileInputStream) { |
|||
try { |
|||
mFileInputStream.close(); |
|||
} catch (IOException e) { |
|||
e.printStackTrace(); |
|||
} |
|||
mFileInputStream = null; |
|||
} |
|||
|
|||
if (null != mFileOutputStream) { |
|||
try { |
|||
mFileOutputStream.close(); |
|||
} catch (IOException e) { |
|||
e.printStackTrace(); |
|||
} |
|||
mFileOutputStream = null; |
|||
} |
|||
|
|||
mOnSerialPortDataListener = null; |
|||
} |
|||
|
|||
/** |
|||
* 添加数据通信监听 |
|||
* |
|||
* @param listener listener |
|||
* @return SerialPortManager |
|||
*/ |
|||
public SerialPortManager setOnSerialPortDataListener(OnSerialPortDataListener listener) { |
|||
mOnSerialPortDataListener = listener; |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* 开启发送消息的线程 |
|||
*/ |
|||
public void startSendThread() { |
|||
// 开启发送消息的线程 |
|||
mSendingHandlerThread = new HandlerThread("mSendingHandlerThread"); |
|||
mSendingHandlerThread.start(); |
|||
// Handler |
|||
mSendingHandler = new Handler(mSendingHandlerThread.getLooper()) { |
|||
@Override |
|||
public void handleMessage(Message msg) { |
|||
byte[] sendBytes = (byte[]) msg.obj; |
|||
|
|||
if (null != mFileOutputStream && null != sendBytes && 0 < sendBytes.length) { |
|||
try { |
|||
mFileOutputStream.write(sendBytes); |
|||
if (null != mOnSerialPortDataListener) { |
|||
mOnSerialPortDataListener.onDataSent(sendBytes); |
|||
} |
|||
} catch (IOException e) { |
|||
e.printStackTrace(); |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
} |
|||
|
|||
/** |
|||
* 停止发送消息线程 |
|||
*/ |
|||
public void stopSendThread() { |
|||
mSendingHandler = null; |
|||
if (null != mSendingHandlerThread) { |
|||
mSendingHandlerThread.interrupt(); |
|||
mSendingHandlerThread.quit(); |
|||
mSendingHandlerThread = null; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 开启接收消息的线程 |
|||
*/ |
|||
public void startReadThread(int type) { |
|||
mSerialPortReadThread = new SerialPortReadThread(mFileInputStream, type) { |
|||
@Override |
|||
public void onDataReceived(byte[] bytes) { |
|||
if (null != mOnSerialPortDataListener) { |
|||
mOnSerialPortDataListener.onDataReceived(bytes); |
|||
} |
|||
} |
|||
}; |
|||
mSerialPortReadThread.startRead(); |
|||
} |
|||
|
|||
/** |
|||
* 停止接收消息的线程 |
|||
*/ |
|||
public void stopReadThread() { |
|||
if (null != mSerialPortReadThread) { |
|||
mSerialPortReadThread.stopRead(); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 发送数据 |
|||
* |
|||
* @param sendBytes 发送数据 |
|||
* @return 发送是否成功 |
|||
*/ |
|||
public boolean sendBytes(byte[] sendBytes) { |
|||
if (null != mFd && null != mFileInputStream && null != mFileOutputStream) { |
|||
if (null != mSendingHandler) { |
|||
Message message = Message.obtain(); |
|||
message.obj = sendBytes; |
|||
return mSendingHandler.sendMessage(message); |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
} |
@ -0,0 +1,23 @@ |
|||
package com.kongqw.serialportlibrary.listener; |
|||
|
|||
/** |
|||
* Created by Kongqw on 2017/11/14. |
|||
* 串口消息监听 |
|||
*/ |
|||
|
|||
public interface OnSerialPortDataListener { |
|||
|
|||
/** |
|||
* 数据接收 |
|||
* |
|||
* @param bytes 接收到的数据 |
|||
*/ |
|||
void onDataReceived(byte[] bytes); |
|||
|
|||
/** |
|||
* 数据发送 |
|||
* |
|||
* @param bytes 发送的数据 |
|||
*/ |
|||
void onDataSent(byte[] bytes); |
|||
} |
@ -0,0 +1,231 @@ |
|||
package com.kongqw.serialportlibrary.thread; |
|||
|
|||
import android.util.Log; |
|||
|
|||
import com.kongqw.serialportlibrary.utils.ByteUtils; |
|||
|
|||
import java.io.IOException; |
|||
import java.io.InputStream; |
|||
|
|||
/** |
|||
* Created by Kongqw on 2017/11/14. |
|||
* 串口消息读取线程 |
|||
*/ |
|||
|
|||
public abstract class SerialPortReadThread extends Thread { |
|||
public abstract void onDataReceived(byte[] bytes); |
|||
|
|||
private InputStream mInputStream; |
|||
private final byte[] mReadBuffer; |
|||
public boolean isStopThread = false; |
|||
private int type = 0; |
|||
|
|||
public SerialPortReadThread(InputStream inputStream, int type) { |
|||
mInputStream = inputStream; |
|||
mReadBuffer = new byte[1024]; |
|||
this.type = type; |
|||
} |
|||
|
|||
@Override |
|||
public void run() { |
|||
super.run(); |
|||
if (type == 485) { |
|||
Log.e("ReadThread", "type 485"); |
|||
while (!isStopThread) { |
|||
try { |
|||
if (null == mInputStream) { |
|||
return; |
|||
} |
|||
|
|||
Log.e("ReadThread", "type 1111"); |
|||
int size = mInputStream.read(mReadBuffer); |
|||
Log.e("ReadThread size", size + ""); |
|||
if (size <= 0) { |
|||
return; |
|||
} |
|||
Log.e("ReadThread", "type 2222"); |
|||
byte[] readBytes = new byte[size]; |
|||
System.arraycopy(mReadBuffer, 0, readBytes, 0, size); |
|||
onDataReceived(readBytes); |
|||
Log.e("ReadThread", "onDataReceived"); |
|||
} catch (IOException e) { |
|||
e.printStackTrace(); |
|||
return; |
|||
} |
|||
} |
|||
} else { |
|||
int rLen = 0; |
|||
int dLen = 0; |
|||
byte[] crc = new byte[]{0}; |
|||
byte[] tmp1 = new byte[]{0}; |
|||
byte[] tmp2 = new byte[]{0}; |
|||
|
|||
while (!isStopThread) { |
|||
try { |
|||
if (mInputStream == null) { |
|||
return; |
|||
} |
|||
if (!isHead()) { |
|||
continue; |
|||
} |
|||
|
|||
crc[0] = 0x55; |
|||
crc[0] ^= 0xAA; |
|||
//接收 cmd |
|||
rLen = recvBuffer(tmp1, 1, 600); |
|||
if (rLen != 1) { |
|||
continue; |
|||
} |
|||
crc[0] ^= tmp1[0]; |
|||
if (tmp1[0] == 0x30) { |
|||
//接收 数据长度 |
|||
rLen = recvBuffer(tmp1, 1, 600); |
|||
if (rLen != 1) { |
|||
continue; |
|||
} |
|||
//接收 数据长度 |
|||
rLen = recvBuffer(tmp2, 1, 600); |
|||
if (rLen != 1) { |
|||
continue; |
|||
} |
|||
crc[0] ^= tmp1[0]; |
|||
crc[0] ^= tmp2[0]; |
|||
dLen = tmp1[0] & 0xff; |
|||
dLen |= ((tmp2[0] << 8) & 0xffff); |
|||
//dLen = ((uint)(tmp1[0])) | (tmp2[0] << 8); |
|||
byte[] data = new byte[dLen]; |
|||
if (dLen > 0) { |
|||
rLen = recvBuffer(data, dLen, 600); |
|||
if (rLen != dLen) { |
|||
continue; |
|||
} |
|||
} |
|||
//接收 crc |
|||
rLen = recvBuffer(tmp1, 1, 600); |
|||
if (rLen != 1) { |
|||
continue; |
|||
} |
|||
for (int i = 0; i < dLen; i++) { |
|||
crc[0] ^= data[i]; |
|||
} |
|||
//crc校验 |
|||
if (!ByteUtils.bytesToHexString(tmp1).equals(ByteUtils.bytesToHexString(crc))) { |
|||
continue; |
|||
} |
|||
onDataReceived(data); |
|||
} else { |
|||
Log.e("idhead", "no 0x30"); |
|||
} |
|||
} catch (Exception e) { |
|||
e.printStackTrace(); |
|||
return; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void startRead() { |
|||
isStopThread = false; |
|||
start(); |
|||
} |
|||
|
|||
|
|||
public void stopRead() { |
|||
isStopThread = true; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 关闭线程 释放资源 |
|||
*/ |
|||
public void release() { |
|||
stopRead(); |
|||
|
|||
if (null != mInputStream) { |
|||
try { |
|||
mInputStream.close(); |
|||
mInputStream = null; |
|||
} catch (IOException e) { |
|||
e.printStackTrace(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private int recvBuffer(byte[] buffer, int size, int msTimeout) { |
|||
long stime = System.currentTimeMillis(); |
|||
int rLen = 0, offset = 0; |
|||
byte[] rBuffer = new byte[1024]; |
|||
byte[] cache = new byte[1024]; |
|||
try { |
|||
do { |
|||
if (mInputStream.available() <= 0) { |
|||
Thread.sleep(10); |
|||
continue; |
|||
} |
|||
rLen = mInputStream.read(rBuffer, 0, Math.min(size, rBuffer.length)); |
|||
if (rLen <= 0) { |
|||
Thread.sleep(10); |
|||
continue; |
|||
} |
|||
stime = System.currentTimeMillis(); |
|||
|
|||
if (offset + rLen > cache.length) { |
|||
byte[] tmp = new byte[offset]; |
|||
System.arraycopy(cache, 0, tmp, 0, offset); |
|||
cache = new byte[(offset + rLen) * 2]; |
|||
System.arraycopy(tmp, 0, cache, 0, offset); |
|||
} |
|||
System.arraycopy(rBuffer, 0, cache, offset, rLen); |
|||
offset += rLen; |
|||
if (offset >= size) { |
|||
System.arraycopy(cache, 0, buffer, 0, size); |
|||
break; |
|||
} |
|||
} while (msTimeout > 0 && System.currentTimeMillis() - stime < msTimeout); |
|||
} catch (Exception e) { |
|||
return offset; |
|||
} |
|||
return offset; |
|||
} |
|||
|
|||
private boolean isHead() { |
|||
byte[] head1 = new byte[]{0}; |
|||
byte[] head2 = new byte[]{0}; |
|||
|
|||
while (true) { |
|||
if (head1[0] != (byte) 0x55) { |
|||
int rLen = recvBuffer(head1, 1, 500); |
|||
if (rLen != 1) { |
|||
try { |
|||
Thread.sleep(500); |
|||
} catch (InterruptedException e) { |
|||
e.printStackTrace(); |
|||
return false; |
|||
} |
|||
continue; |
|||
} |
|||
} |
|||
|
|||
int rLen = recvBuffer(head2, 1, 500); |
|||
if (rLen != 1) { |
|||
try { |
|||
Thread.sleep(10); |
|||
} catch (InterruptedException e) { |
|||
e.printStackTrace(); |
|||
return false; |
|||
} |
|||
continue; |
|||
} |
|||
|
|||
if (head1[0] != (byte) 0x55) { |
|||
head1[0] = head2[0]; |
|||
continue; |
|||
} |
|||
if (head2[0] != (byte) 0xAA) { |
|||
head1[0] = head2[0]; |
|||
continue; |
|||
} |
|||
return true; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,487 @@ |
|||
package com.kongqw.serialportlibrary.utils; |
|||
|
|||
import java.io.ByteArrayOutputStream; |
|||
import java.io.DataOutputStream; |
|||
import java.io.IOException; |
|||
import java.io.InputStream; |
|||
import java.util.ArrayList; |
|||
import java.util.Arrays; |
|||
|
|||
/** |
|||
* @author wangsir |
|||
* <p> |
|||
* 2017年9月21日 |
|||
*/ |
|||
public class ByteUtils { |
|||
/** |
|||
* 将int转为高端字节序排列的byte数组(Java内存存放顺序) |
|||
* |
|||
* @param n |
|||
* @return |
|||
*/ |
|||
public static byte[] int2ByteArray(int n) { |
|||
byte[] byteArray = null; |
|||
try { |
|||
ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); |
|||
DataOutputStream dataOut = new DataOutputStream(byteOut); |
|||
dataOut.writeInt(n); |
|||
byteArray = byteOut.toByteArray(); |
|||
Arrays.toString(byteArray); |
|||
} catch (IOException e) { |
|||
e.printStackTrace(); |
|||
} |
|||
return byteArray; |
|||
} |
|||
|
|||
/** |
|||
* 将int转为高字节在前,低字节在后的byte数组 |
|||
* |
|||
* @param n int |
|||
* @return byte[] |
|||
*/ |
|||
public static byte[] int2Hbytes(int n) { |
|||
byte[] b = new byte[4]; |
|||
b[3] = (byte) (n & 0xff); |
|||
b[2] = (byte) (n >> 8 & 0xff); |
|||
b[1] = (byte) (n >> 16 & 0xff); |
|||
b[0] = (byte) (n >> 24 & 0xff); |
|||
|
|||
return b; |
|||
} |
|||
|
|||
/** |
|||
* 将int转为高字节在前,低字节在后的byte数组,一字节 |
|||
* |
|||
* @param n int |
|||
* @return byte[] |
|||
*/ |
|||
public static byte[] int2Hbytes1byte(int n) { |
|||
byte[] b = new byte[1]; |
|||
b[0] = (byte) (n & 0xff); |
|||
return b; |
|||
} |
|||
|
|||
/** |
|||
* 将short转为高字节在前,低字节在后的byte数组 |
|||
* |
|||
* @param n short |
|||
* @return byte[] |
|||
*/ |
|||
public static byte[] short2Hbytes(short n) { |
|||
byte[] b = new byte[2]; |
|||
b[1] = (byte) (n & 0xff); |
|||
b[0] = (byte) (n >> 8 & 0xff); |
|||
return b; |
|||
} |
|||
|
|||
/** |
|||
* 以下 是整型数 和 网络字节序的 byte[] 数组之间的转换 |
|||
* |
|||
* @param n |
|||
* @return |
|||
*/ |
|||
public static byte[] long2Hbytes(long n) { |
|||
byte[] b = new byte[8]; |
|||
b[7] = (byte) (n & 0xff); |
|||
b[6] = (byte) (n >> 8 & 0xff); |
|||
b[5] = (byte) (n >> 16 & 0xff); |
|||
b[4] = (byte) (n >> 24 & 0xff); |
|||
b[3] = (byte) (n >> 32 & 0xff); |
|||
b[2] = (byte) (n >> 40 & 0xff); |
|||
b[1] = (byte) (n >> 48 & 0xff); |
|||
b[0] = (byte) (n >> 56 & 0xff); |
|||
return b; |
|||
} |
|||
|
|||
public static byte[] long2H6bytes(long n) { |
|||
byte[] b = new byte[6]; |
|||
b[5] = (byte) (n & 0xff); |
|||
b[4] = (byte) (n >> 8 & 0xff); |
|||
b[3] = (byte) (n >> 16 & 0xff); |
|||
b[2] = (byte) (n >> 24 & 0xff); |
|||
b[1] = (byte) (n >> 32 & 0xff); |
|||
b[0] = (byte) (n >> 40 & 0xff); |
|||
return b; |
|||
} |
|||
|
|||
public static byte[] long2H4bytes(long n) { |
|||
byte[] b = new byte[4]; |
|||
b[3] = (byte) (n & 0xff); |
|||
b[2] = (byte) (n >> 8 & 0xff); |
|||
b[1] = (byte) (n >> 16 & 0xff); |
|||
b[0] = (byte) (n >> 24 & 0xff); |
|||
return b; |
|||
} |
|||
|
|||
public static byte[] unlong2H4bytes(long n) { |
|||
byte[] b = new byte[4]; |
|||
b[0] = (byte) (n & 0xff); |
|||
b[1] = (byte) (n >> 8 & 0xff); |
|||
b[2] = (byte) (n >> 16 & 0xff); |
|||
b[3] = (byte) (n >> 24 & 0xff); |
|||
return b; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 合并数组 |
|||
* |
|||
* @param first |
|||
* @param rest |
|||
* @return |
|||
*/ |
|||
public static byte[] concatBytes(byte[] first, byte[]... rest) { |
|||
int totalLength = first.length; |
|||
for (byte[] array : rest) { |
|||
if (null != array) { |
|||
totalLength += array.length; |
|||
} |
|||
} |
|||
byte[] result = Arrays.copyOf(first, totalLength); |
|||
int offset = first.length; |
|||
for (byte[] array : rest) { |
|||
if (null != array) { |
|||
System.arraycopy(array, 0, result, offset, array.length); |
|||
offset += array.length; |
|||
} |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
/** |
|||
* byte数组转为十六进制字符串 |
|||
* |
|||
* @param bytes |
|||
* @return |
|||
*/ |
|||
public static String byte2Hex(byte[] bytes) { |
|||
StringBuffer hexString = new StringBuffer(); |
|||
for (int i = 0; i < bytes.length; i++) { |
|||
String hex = Integer.toHexString(0xff & bytes[i]); |
|||
|
|||
if (hex.length() == 1) { |
|||
hexString.append('0'); |
|||
} |
|||
hexString.append(hex); |
|||
} |
|||
return hexString.toString(); |
|||
} |
|||
|
|||
/** |
|||
* Convert hex string to byte[] |
|||
* |
|||
* @param hexString the hex string |
|||
* @return byte[] |
|||
*/ |
|||
public static byte[] hex2Byte(String hexString) { |
|||
if (hexString == null || hexString.equals("")) { |
|||
return null; |
|||
} |
|||
hexString = hexString.toUpperCase().replace(" ", ""); |
|||
int length = hexString.length() / 2; |
|||
char[] hexChars = hexString.toCharArray(); |
|||
byte[] d = new byte[length]; |
|||
for (int i = 0; i < length; i++) { |
|||
int pos = i * 2; |
|||
d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1])); |
|||
} |
|||
return d; |
|||
} |
|||
|
|||
/** |
|||
* Convert char to byte |
|||
* |
|||
* @param c char |
|||
* @return byte |
|||
*/ |
|||
private static byte charToByte(char c) { |
|||
return (byte) "0123456789ABCDEF".indexOf(c); |
|||
} |
|||
|
|||
/** |
|||
* 输入流转为字节数组 |
|||
*/ |
|||
public static byte[] toByteArray(InputStream input) throws IOException { |
|||
ByteArrayOutputStream output = new ByteArrayOutputStream(); |
|||
byte[] buffer = new byte[4096]; |
|||
int n = 0; |
|||
while (-1 != (n = input.read(buffer))) { |
|||
output.write(buffer, 0, n); |
|||
} |
|||
return output.toByteArray(); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* bytes字符串转换为Byte值 |
|||
* |
|||
* @param src src Byte字符串,每个Byte之间没有分隔符 |
|||
* @return byte[] |
|||
*/ |
|||
public static byte[] hexStr2Bytes(String src) { |
|||
int m = 0, n = 0; |
|||
int l = src.length() / 2; |
|||
byte[] ret = new byte[l]; |
|||
for (int i = 0; i < l; i++) { |
|||
m = i * 2 + 1; |
|||
n = m + 1; |
|||
ret[i] = Byte.decode("0x" + src.substring(i * 2, m) + src.substring(m, n)); |
|||
} |
|||
return ret; |
|||
} |
|||
|
|||
// /** |
|||
// * 字节数组转换成16进制字符串 |
|||
// * @param bytes 字节数组 |
|||
// * @return 16进制字符串 |
|||
// */ |
|||
// public static String hexEncode(byte[] bytes) { |
|||
// if (bytes == null || bytes.length <= 0) { |
|||
// return null; |
|||
// } |
|||
// return new String(Hex.encodeHex(bytes)); //Hex.encodeHex(bytes, false) |
|||
// } |
|||
// |
|||
// /** |
|||
// * 16进制字符串转换成字节数组 |
|||
// * @param hexStr 16进制字符串 |
|||
// * @return 字节数组 |
|||
// */ |
|||
// public static byte[] hexDecode(String hexStr) { |
|||
// if (hexStr == null || "".equals(hexStr)) { |
|||
// return null; |
|||
// } |
|||
// try { |
|||
// char[] cs = hexStr.toCharArray(); |
|||
// return Hex.decodeHex(cs); |
|||
// } catch (DecoderException e) { |
|||
// e.printStackTrace(); |
|||
// } |
|||
// return null; |
|||
// } |
|||
|
|||
|
|||
/** |
|||
* 字符串转换成十六进制字符串 |
|||
* |
|||
* @param s s为待转换的ASCII字符串 |
|||
*/ |
|||
public static String str2HexStr(String s) { |
|||
|
|||
String str = ""; |
|||
for (int i = 0; i < s.length(); i++) { |
|||
int ch = (int) s.charAt(i); |
|||
String s4 = Integer.toHexString(ch); |
|||
str = str + s4; |
|||
} |
|||
return str; |
|||
} |
|||
|
|||
public static String int2HexStr(Integer n) { |
|||
|
|||
String str = Integer.toHexString(n); |
|||
while (str.length() < 18) { |
|||
str = "0" + str; |
|||
} |
|||
return str.toUpperCase(); |
|||
} |
|||
|
|||
public static String binaryToHex(String bin) { |
|||
String str = Long.toHexString(Long.parseLong(bin, 2)); |
|||
while (str.length() < 4) { |
|||
str = "0" + str; |
|||
} |
|||
return str.toUpperCase(); |
|||
} |
|||
|
|||
/** |
|||
* 16进制的字符串转byte数组 |
|||
* |
|||
* @param src |
|||
* @return |
|||
*/ |
|||
public static byte[] str16ToBytes(String src) { |
|||
int w = 0; |
|||
byte[] bytes_2 = new byte[src.length() / 2]; |
|||
for (int i = 0; i < src.length(); i++) { |
|||
String zz = src.substring(i, i + 2); |
|||
byte aaa = (byte) Integer.parseInt(zz, 16); |
|||
bytes_2[w] = aaa; |
|||
i++; |
|||
|
|||
w = w + 1; |
|||
} |
|||
|
|||
return bytes_2; |
|||
} |
|||
|
|||
/** |
|||
* 16进制转10进制数字 |
|||
* |
|||
* @param src |
|||
* @return |
|||
*/ |
|||
public static int str16ToInt10(String src) { |
|||
return Integer.parseInt(src, 16); |
|||
} |
|||
|
|||
/** |
|||
* 16进制转10进制数字 |
|||
* |
|||
* @param src |
|||
* @return |
|||
*/ |
|||
public static long str16ToLong10(String src) { |
|||
return Long.parseLong(src, 16); |
|||
} |
|||
|
|||
public static String convertStringToHex(String str) { |
|||
char[] chars = str.toCharArray(); |
|||
StringBuffer hex = new StringBuffer(); |
|||
for (int i = 0; i < chars.length; i++) { |
|||
hex.append(Integer.toHexString((int) chars[i])); |
|||
} |
|||
return hex.toString(); |
|||
} |
|||
|
|||
public static String convertHexToString(String hex) { |
|||
hex = hex.replace(" ", ""); |
|||
StringBuilder sb = new StringBuilder(); |
|||
StringBuilder temp = new StringBuilder(); |
|||
|
|||
//49204c6f7665204a617661 split into two characters 49, 20, 4c... |
|||
for (int i = 0; i < hex.length() - 1; i += 2) { |
|||
|
|||
//grab the hex in pairs |
|||
String output = hex.substring(i, (i + 2)); |
|||
//convert hex to decimal |
|||
int decimal = Integer.parseInt(output, 16); |
|||
//convert the decimal to character |
|||
sb.append((char) decimal); |
|||
|
|||
temp.append(decimal); |
|||
} |
|||
|
|||
return sb.toString(); |
|||
} |
|||
|
|||
/** |
|||
* @param |
|||
* @ClassName |
|||
* @Description : 功能说明 |
|||
* @Return : |
|||
* @Author : li |
|||
* @Date : 2021/3/25 13:48 |
|||
*/ |
|||
public static byte[] hexStringToByteArray(String hexString) { |
|||
hexString = hexString.replaceAll(" ", ""); |
|||
int len = hexString.length(); |
|||
byte[] bytes = new byte[len / 2]; |
|||
for (int i = 0; i < len; i += 2) { |
|||
// 两位一组,表示一个字节,把这样表示的16进制字符串,还原成一个字节 |
|||
bytes[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character |
|||
.digit(hexString.charAt(i + 1), 16)); |
|||
} |
|||
return bytes; |
|||
} |
|||
|
|||
/** |
|||
* @param |
|||
* @ClassName |
|||
* @Description : 功能说明 |
|||
* @Return : |
|||
* @Author : li |
|||
* @Date : 2021/3/25 13:48 |
|||
*/ |
|||
public static String bytesToHexString(byte[] bytes) { |
|||
StringBuilder sb = new StringBuilder(); |
|||
for (int i = 0; i < bytes.length; i++) { |
|||
String hex = Integer.toHexString(0xFF & bytes[i]); |
|||
if (hex.length() == 1) { |
|||
sb.append('0'); |
|||
} |
|||
sb.append(hex); |
|||
} |
|||
return sb.toString(); |
|||
} |
|||
|
|||
/** |
|||
* 从16进制字符串中获取2进制为1的序列号 |
|||
* |
|||
* @param src |
|||
* @return |
|||
*/ |
|||
public static ArrayList Str16Get1(String src) { |
|||
byte[] b_p = ByteUtils.str16ToBytes(src); |
|||
ArrayList list = new ArrayList<>(); |
|||
int w = 0; |
|||
for (int i = 0; i < src.length() / 2; i++) { |
|||
for (int j = 7; j >= 0; j--) { |
|||
if ((b_p[i] & (1 << j)) != 0) { |
|||
int pp = i * 8 + 8 - j; |
|||
System.out.println("pppp=======" + pp); |
|||
list.add(w, pp); |
|||
w = w + 1; |
|||
} else { |
|||
|
|||
} |
|||
} |
|||
|
|||
} |
|||
return list; |
|||
} |
|||
|
|||
/** |
|||
* 异或校验 |
|||
* |
|||
* @param bytes |
|||
* @return |
|||
*/ |
|||
public static byte[] bytesXorCrc(byte[] bytes) { |
|||
byte[] crc = new byte[1];// 异或校验 |
|||
crc[0] = bytes[0]; |
|||
for (int i = 1; i < bytes.length; i++) { |
|||
crc[0] ^= bytes[i]; |
|||
} |
|||
return crc; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 和校验 |
|||
* |
|||
* @param data |
|||
* @return |
|||
*/ |
|||
public static String makeChecksum(String data) { |
|||
data = data.replace(" ", ""); |
|||
if (data == null || data.equals("")) { |
|||
return ""; |
|||
} |
|||
int total = 0; |
|||
int len = data.length(); |
|||
int num = 0; |
|||
while (num < len) { |
|||
String s = data.substring(num, num + 2); |
|||
total += Integer.parseInt(s, 16); |
|||
num = num + 2; |
|||
} |
|||
/** |
|||
* 用256求余最大是255,即16进制的FF |
|||
*/ |
|||
int mod = total % 256; |
|||
String hex = Integer.toHexString(mod); |
|||
len = hex.length(); |
|||
// 如果不够校验位的长度,补0,这里用的是两位校验 |
|||
if (len < 2) { |
|||
hex = "0" + hex; |
|||
} |
|||
hex = hex.toUpperCase(); |
|||
if (hex.equals("7E")) { |
|||
hex = "7F 01"; |
|||
} else if (hex.equals("7F")) { |
|||
hex = "7F 02"; |
|||
} |
|||
return hex; |
|||
} |
|||
} |
@ -0,0 +1,3 @@ |
|||
<resources> |
|||
<string name="app_name">SerialPortLibrary</string> |
|||
</resources> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue