5 changed files with 605 additions and 12 deletions
-
3app/build.gradle
-
83app/src/main/java/com/ouxuan/oxface/DebugActivity.java
-
118app/src/main/java/com/ouxuan/oxface/device/GateABController.java
-
406app/src/main/java/com/ouxuan/oxface/device/Ox485.java
-
7app/src/main/res/layout/activity_debug.xml
@ -0,0 +1,406 @@ |
|||||
|
package com.ouxuan.oxface.device; |
||||
|
|
||||
|
import android.content.Context; |
||||
|
import android.os.Handler; |
||||
|
import android.os.Looper; |
||||
|
import android.util.Log; |
||||
|
|
||||
|
import com.kongqw.serialportlibrary.SerialPortManager; |
||||
|
import com.kongqw.serialportlibrary.listener.OnSerialPortDataListener; |
||||
|
import com.kongqw.serialportlibrary.utils.ByteUtils; |
||||
|
import com.ouxuan.oxface.utils.LogManager; |
||||
|
|
||||
|
import java.io.File; |
||||
|
import java.util.concurrent.CompletableFuture; |
||||
|
import java.util.concurrent.TimeUnit; |
||||
|
|
||||
|
/** |
||||
|
* Ox485 串口通信模块 |
||||
|
* 基于kongqw/SerialPortLibrary实现485通信功能 |
||||
|
* 主要用于获取摄像头人数等485设备通信 |
||||
|
* |
||||
|
* @author AI Assistant |
||||
|
* @version 1.0 |
||||
|
* @date 2024/09/12 |
||||
|
*/ |
||||
|
public class Ox485 { |
||||
|
|
||||
|
private static final String TAG = "Ox485"; |
||||
|
|
||||
|
// 485通信配置 |
||||
|
private static final String DEFAULT_SERIAL_PORT_PATH = "/dev/ttyS6"; |
||||
|
private static final int DEFAULT_BAUD_RATE = 9600; |
||||
|
private static final int DEFAULT_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 Ox485 instance; |
||||
|
|
||||
|
// 485通信相关 |
||||
|
private SerialPortManager serialPortManager; |
||||
|
private Handler mainHandler; |
||||
|
private boolean gateCamera485OxOn = false; |
||||
|
private Context context; |
||||
|
|
||||
|
// 数据接收相关 |
||||
|
private long lastRecvTime = 0; |
||||
|
private Handler timeoutHandler; |
||||
|
private Runnable timeoutRunnable; |
||||
|
|
||||
|
/** |
||||
|
* 485通信回调接口 |
||||
|
*/ |
||||
|
public interface PeopleNumCallback { |
||||
|
void onSuccess(int peopleNum); |
||||
|
void onError(String errorMessage); |
||||
|
} |
||||
|
|
||||
|
private Ox485() { |
||||
|
mainHandler = new Handler(Looper.getMainLooper()); |
||||
|
timeoutHandler = new Handler(Looper.getMainLooper()); |
||||
|
serialPortManager = new SerialPortManager(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取Ox485单例实例 |
||||
|
* @return Ox485实例 |
||||
|
*/ |
||||
|
public static Ox485 getInstance() { |
||||
|
if (instance == null) { |
||||
|
synchronized (Ox485.class) { |
||||
|
if (instance == null) { |
||||
|
instance = new Ox485(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return instance; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 初始化Ox485模块 |
||||
|
* @param context 上下文 |
||||
|
*/ |
||||
|
public void initialize(Context context) { |
||||
|
this.context = context; |
||||
|
LogManager.logInfo(TAG, "Ox485模块初始化完成"); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 设置485模式开关 |
||||
|
* @param enabled 是否启用485模式 |
||||
|
*/ |
||||
|
public void setGateCamera485OxOn(boolean enabled) { |
||||
|
this.gateCamera485OxOn = 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; |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
// 检查串口是否已经打开 |
||||
|
if (serialPortManager.isOpen()) { |
||||
|
LogManager.logInfo(TAG, "串口已打开,直接发送命令"); |
||||
|
sendCommandAndWaitResponse(callback); |
||||
|
} else { |
||||
|
LogManager.logInfo(TAG, "串口未打开,尝试打开串口"); |
||||
|
openSerialPortAndSend(callback); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
String errorMsg = "485通信异常: " + e.getMessage(); |
||||
|
LogManager.logError(TAG, errorMsg, e); |
||||
|
if (callback != null) { |
||||
|
mainHandler.post(() -> callback.onError(errorMsg)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 打开串口并发送命令 |
||||
|
* @param callback 结果回调 |
||||
|
*/ |
||||
|
private void openSerialPortAndSend(PeopleNumCallback callback) { |
||||
|
try { |
||||
|
// 配置串口参数 |
||||
|
File serialPortFile = new File(DEFAULT_SERIAL_PORT_PATH); |
||||
|
boolean openResult = serialPortManager.openSerialPort(serialPortFile, DEFAULT_BAUD_RATE); |
||||
|
|
||||
|
LogManager.logInfo(TAG, "串口打开结果: " + openResult + ", 路径: " + DEFAULT_SERIAL_PORT_PATH + ", 波特率: " + DEFAULT_BAUD_RATE); |
||||
|
|
||||
|
if (!openResult) { |
||||
|
String errorMsg = "打开串口失败: " + DEFAULT_SERIAL_PORT_PATH; |
||||
|
LogManager.logError(TAG, errorMsg); |
||||
|
if (callback != null) { |
||||
|
mainHandler.post(() -> callback.onError(errorMsg)); |
||||
|
} |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 启动读写线程 |
||||
|
serialPortManager.startReadThread(485); |
||||
|
serialPortManager.startSendThread(); |
||||
|
|
||||
|
// 发送命令并等待响应 |
||||
|
sendCommandAndWaitResponse(callback); |
||||
|
|
||||
|
} catch (Exception e) { |
||||
|
String errorMsg = "打开串口异常: " + e.getMessage(); |
||||
|
LogManager.logError(TAG, errorMsg, e); |
||||
|
if (callback != null) { |
||||
|
mainHandler.post(() -> callback.onError(errorMsg)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 发送命令并等待响应 |
||||
|
* @param callback 结果回调 |
||||
|
*/ |
||||
|
private void sendCommandAndWaitResponse(PeopleNumCallback callback) { |
||||
|
// 设置数据监听器 |
||||
|
serialPortManager.setOnSerialPortDataListener(new OnSerialPortDataListener() { |
||||
|
@Override |
||||
|
public void onDataReceived(byte[] bytes) { |
||||
|
long timeInterval = (System.nanoTime() - lastRecvTime) / 1000000; |
||||
|
lastRecvTime = System.nanoTime(); |
||||
|
|
||||
|
String hexData = ByteUtils.bytesToHexString(bytes); |
||||
|
LogManager.logInfo(TAG, "接收到数据: " + hexData + ", 字节数组: " + java.util.Arrays.toString(bytes)); |
||||
|
|
||||
|
// 取消超时处理 |
||||
|
cancelTimeout(); |
||||
|
|
||||
|
// 解析人数数据 |
||||
|
int peopleNum = get485PeopleNum(bytes); |
||||
|
if (peopleNum >= 0) { |
||||
|
LogManager.logInfo(TAG, "485获取人数成功: " + peopleNum); |
||||
|
if (callback != null) { |
||||
|
mainHandler.post(() -> callback.onSuccess(peopleNum)); |
||||
|
} |
||||
|
} else { |
||||
|
String errorMsg = "485数据解析失败,数据: " + hexData; |
||||
|
LogManager.logError(TAG, errorMsg); |
||||
|
if (callback != null) { |
||||
|
mainHandler.post(() -> callback.onError(errorMsg)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onDataSent(byte[] bytes) { |
||||
|
String hexData = ByteUtils.bytesToHexString(bytes); |
||||
|
LogManager.logInfo(TAG, "发送数据: " + hexData); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// 发送HEX命令 |
||||
|
try { |
||||
|
byte[] hexBytes = hexStringToByteArray(HEX_COMMAND_GET_PEOPLE_NUM); |
||||
|
serialPortManager.sendBytes(hexBytes); |
||||
|
LogManager.logInfo(TAG, "发送485人数查询命令: " + HEX_COMMAND_GET_PEOPLE_NUM); |
||||
|
|
||||
|
// 设置超时处理 |
||||
|
setupTimeout(callback); |
||||
|
|
||||
|
} catch (Exception e) { |
||||
|
String errorMsg = "发送485命令异常: " + e.getMessage(); |
||||
|
LogManager.logError(TAG, errorMsg, e); |
||||
|
if (callback != null) { |
||||
|
mainHandler.post(() -> callback.onError(errorMsg)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 设置超时处理 |
||||
|
* @param callback 结果回调 |
||||
|
*/ |
||||
|
private void setupTimeout(PeopleNumCallback callback) { |
||||
|
cancelTimeout(); // 先取消之前的超时任务 |
||||
|
|
||||
|
timeoutRunnable = () -> { |
||||
|
String errorMsg = "485人数查询超时"; |
||||
|
LogManager.logError(TAG, errorMsg); |
||||
|
if (callback != null) { |
||||
|
callback.onError(errorMsg); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
timeoutHandler.postDelayed(timeoutRunnable, TIMEOUT_485_MILLIS); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 取消超时处理 |
||||
|
*/ |
||||
|
private void cancelTimeout() { |
||||
|
if (timeoutHandler != null && timeoutRunnable != null) { |
||||
|
timeoutHandler.removeCallbacks(timeoutRunnable); |
||||
|
timeoutRunnable = null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 解析485人数数据 |
||||
|
* 对应uniapp中的get485PeopleNum方法 |
||||
|
* @param arr 接收到的字节数组 |
||||
|
* @return 人数,-1表示解析失败 |
||||
|
*/ |
||||
|
private int get485PeopleNum(byte[] arr) { |
||||
|
LogManager.logDebug(TAG, "解析485人数数据: " + java.util.Arrays.toString(arr)); |
||||
|
|
||||
|
// 数据校验 |
||||
|
if (arr == null) { |
||||
|
LogManager.logError(TAG, "485数据为空"); |
||||
|
return -1; |
||||
|
} |
||||
|
|
||||
|
if (arr.length != 9) { |
||||
|
LogManager.logError(TAG, "485数据长度错误,期望9字节,实际: " + arr.length + ", 数据: " + java.util.Arrays.toString(arr)); |
||||
|
return -1; |
||||
|
} |
||||
|
|
||||
|
// 检查第3个字节是否为4(表示正确响应) |
||||
|
if (arr[2] != 4) { |
||||
|
LogManager.logError(TAG, "485响应格式错误,第3字节应为4,实际: " + arr[2] + ", 数据: " + java.util.Arrays.toString(arr)); |
||||
|
return -1; |
||||
|
} |
||||
|
|
||||
|
// 提取第5个字节作为人数数据,使用位掩码确保为无符号整数 |
||||
|
int peopleNum = arr[4] & 0xFF; |
||||
|
LogManager.logInfo(TAG, "解析485人数成功: " + peopleNum); |
||||
|
|
||||
|
return peopleNum; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 异步获取摄像头人数(使用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串口"); |
||||
|
if (!serialPortManager.isOpen()) { |
||||
|
LogManager.logInfo(TAG, "串口未打开,需要打开"); |
||||
|
} else { |
||||
|
LogManager.logInfo(TAG, "串口已打开"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 发送自定义485 HEX命令 |
||||
|
* @param hexCommand HEX命令字符串 |
||||
|
*/ |
||||
|
public void send485HexCommand(String hexCommand) { |
||||
|
LogManager.logInfo(TAG, "发送485 HEX命令: " + hexCommand); |
||||
|
try { |
||||
|
if (!serialPortManager.isOpen()) { |
||||
|
LogManager.logError(TAG, "串口未打开,无法发送命令"); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
byte[] hexBytes = hexStringToByteArray(hexCommand); |
||||
|
serialPortManager.sendBytes(hexBytes); |
||||
|
LogManager.logInfo(TAG, "485命令发送完成"); |
||||
|
} catch (Exception e) { |
||||
|
LogManager.logError(TAG, "发送485命令异常: " + e.getMessage(), e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取485串口状态 |
||||
|
* @return 串口状态描述 |
||||
|
*/ |
||||
|
public String get485Status() { |
||||
|
boolean isOpen = serialPortManager.isOpen(); |
||||
|
String status = "串口状态: " + (isOpen ? "已打开" : "已关闭") + |
||||
|
", 路径: " + DEFAULT_SERIAL_PORT_PATH + |
||||
|
", 波特率: " + DEFAULT_BAUD_RATE + |
||||
|
", 485模式: " + (gateCamera485OxOn ? "已启用" : "未启用"); |
||||
|
LogManager.logInfo(TAG, status); |
||||
|
return status; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 关闭485串口 |
||||
|
*/ |
||||
|
public void close485SerialPort() { |
||||
|
LogManager.logInfo(TAG, "关闭485串口"); |
||||
|
try { |
||||
|
cancelTimeout(); |
||||
|
if (serialPortManager != null) { |
||||
|
serialPortManager.close(); |
||||
|
LogManager.logInfo(TAG, "485串口已关闭"); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
LogManager.logError(TAG, "关闭485串口异常: " + e.getMessage(), e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 释放资源 |
||||
|
*/ |
||||
|
public void release() { |
||||
|
LogManager.logInfo(TAG, "释放Ox485资源"); |
||||
|
close485SerialPort(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* HEX字符串转字节数组 |
||||
|
* @param hexString HEX字符串 |
||||
|
* @return 字节数组 |
||||
|
*/ |
||||
|
private byte[] hexStringToByteArray(String hexString) { |
||||
|
int len = hexString.length(); |
||||
|
byte[] data = new byte[len / 2]; |
||||
|
for (int i = 0; i < len; i += 2) { |
||||
|
data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) |
||||
|
+ Character.digit(hexString.charAt(i + 1), 16)); |
||||
|
} |
||||
|
return data; |
||||
|
} |
||||
|
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue