You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
760 lines
28 KiB
760 lines
28 KiB
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 2.0
|
|
* @date 2024/09/15
|
|
*/
|
|
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 final long CONNECTION_CHECK_INTERVAL = 30000; // 30秒检查一次连接状态
|
|
private static final int MAX_RETRY_COUNT = 3; // 最大重试次数
|
|
private static final long RETRY_DELAY = 1000; // 重试延迟1秒
|
|
|
|
// 单例实例
|
|
private static volatile Ox485 instance;
|
|
|
|
// 485通信相关
|
|
private SerialPortManager serialPortManager;
|
|
private Handler mainHandler;
|
|
private boolean gateCamera485OxOn = false;
|
|
private Context context;
|
|
|
|
// 懒加载连接管理相关
|
|
private boolean isSerialPortOpen = false; // 串口是否已打开
|
|
private boolean isConnecting = false; // 是否正在连接中
|
|
private Handler connectionCheckHandler; // 连接检查处理器
|
|
private Runnable connectionCheckRunnable; // 连接检查任务
|
|
private long lastSuccessTime = 0; // 最后一次成功通信时间
|
|
private int currentRetryCount = 0; // 当前重试次数
|
|
|
|
// 数据接收相关
|
|
private long lastRecvTime = 0;
|
|
private Handler timeoutHandler;
|
|
private Runnable timeoutRunnable;
|
|
|
|
/**
|
|
* 485通信回调接口
|
|
*/
|
|
public interface PeopleNumCallback {
|
|
void onSuccess(int peopleNum);
|
|
void onError(String errorMessage);
|
|
}
|
|
|
|
/**
|
|
* 连接回调接口
|
|
*/
|
|
private interface ConnectionCallback {
|
|
void onConnected();
|
|
void onError(String errorMessage);
|
|
}
|
|
|
|
private Ox485() {
|
|
mainHandler = new Handler(Looper.getMainLooper());
|
|
timeoutHandler = new Handler(Looper.getMainLooper());
|
|
connectionCheckHandler = new Handler(Looper.getMainLooper());
|
|
serialPortManager = new SerialPortManager();
|
|
|
|
// 初始化连接检查任务
|
|
initConnectionCheckTask();
|
|
}
|
|
|
|
/**
|
|
* 获取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, "15:45:22.734 Ox485 D Ox485模块初始化完成,采用懒加载连接管理模式");
|
|
|
|
// 启动定时连接检查
|
|
startConnectionCheck();
|
|
}
|
|
|
|
/**
|
|
* 设置485模式开关
|
|
* @param enabled 是否启用485模式
|
|
*/
|
|
public void setGateCamera485OxOn(boolean enabled) {
|
|
this.gateCamera485OxOn = enabled;
|
|
LogManager.logInfo(TAG, "15:45:22.734 Ox485 D 设置485模式开关: " + enabled);
|
|
}
|
|
|
|
/**
|
|
* 获取485模式开关状态
|
|
* @return true表示485模式已启用
|
|
*/
|
|
public boolean isGateCamera485OxOn() {
|
|
return gateCamera485OxOn;
|
|
}
|
|
|
|
/**
|
|
* 通过485获取摄像头人数
|
|
* 对应uniapp中的sendHex485ForPeopleNum方法
|
|
* 采用懒加载连接管理,首次使用时才打开串口
|
|
* @param callback 结果回调
|
|
*/
|
|
public void sendHex485ForPeopleNum(PeopleNumCallback callback) {
|
|
LogManager.logInfo(TAG, "15:45:22.734 Ox485 D 开始通过485获取摄像头人数(懒加载模式)");
|
|
|
|
if (!gateCamera485OxOn) {
|
|
String errorMsg = "485模式未开启";
|
|
LogManager.logError(TAG, errorMsg);
|
|
if (callback != null) {
|
|
mainHandler.post(() -> callback.onError(errorMsg));
|
|
}
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// 懒加载:检查连接状态,需要时才建立连接
|
|
ensureConnection(new ConnectionCallback() {
|
|
@Override
|
|
public void onConnected() {
|
|
// 连接成功,发送命令
|
|
sendCommandAndWaitResponse(callback);
|
|
}
|
|
|
|
@Override
|
|
public void onError(String errorMessage) {
|
|
// 连接失败
|
|
if (callback != null) {
|
|
mainHandler.post(() -> callback.onError(errorMessage));
|
|
}
|
|
}
|
|
});
|
|
} 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 sendCommandAndWaitResponse(PeopleNumCallback callback) {
|
|
// 发送前检查串口状态
|
|
if (!isSerialPortReallyOpen()) {
|
|
String errorMsg = "串口未打开,无法发送命令";
|
|
LogManager.logError(TAG, errorMsg);
|
|
isSerialPortOpen = false;
|
|
if (callback != null) {
|
|
mainHandler.post(() -> callback.onError(errorMsg));
|
|
}
|
|
return;
|
|
}
|
|
|
|
LogManager.logInfo(TAG, "485串口状态检查通过,开始发送命令");
|
|
|
|
// 设置数据监听器
|
|
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, "Ox485接收到数据: " + hexData + ", 字节数组: " + java.util.Arrays.toString(bytes) + ", 数据长度: " + bytes.length);
|
|
|
|
// 数据包过滤:只处理以[15, 3, 4, 0]开头的正常响应数据包
|
|
if (!isNormalResponsePacket(bytes)) {
|
|
LogManager.logInfo(TAG, "Ox485接收到非正常响应数据包,已过滤: " + hexData + ", 期望格式: [15, 3, 4, 0]");
|
|
return;
|
|
}
|
|
|
|
// 取消超时处理
|
|
cancelTimeout();
|
|
|
|
// 标记成功通信(用于连接健康检查)
|
|
markSuccessfulCommunication();
|
|
|
|
// 解析人数数据
|
|
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, "Ox485发送数据成功: " + hexData + ", 数据长度: " + bytes.length);
|
|
}
|
|
});
|
|
|
|
// 发送HEX命令
|
|
try {
|
|
byte[] hexBytes = hexStringToByteArray(HEX_COMMAND_GET_PEOPLE_NUM);
|
|
|
|
// 验证要发送的数据
|
|
LogManager.logInfo(TAG, "准备发送485命令: " + HEX_COMMAND_GET_PEOPLE_NUM + ", 转换后字节数组: " + java.util.Arrays.toString(hexBytes));
|
|
|
|
boolean sendResult = serialPortManager.sendBytes(hexBytes);
|
|
LogManager.logInfo(TAG, "485命令发送结果: " + sendResult);
|
|
|
|
if (!sendResult) {
|
|
String errorMsg = "485命令发送失败,可能串口连接异常";
|
|
LogManager.logError(TAG, errorMsg);
|
|
isSerialPortOpen = false;
|
|
if (callback != null) {
|
|
mainHandler.post(() -> callback.onError(errorMsg));
|
|
}
|
|
return;
|
|
}
|
|
|
|
// 设置超时处理
|
|
setupTimeout(callback);
|
|
|
|
} catch (Exception e) {
|
|
String errorMsg = "发送485命令异常: " + e.getMessage();
|
|
LogManager.logError(TAG, errorMsg, e);
|
|
|
|
// 发送失败可能是连接问题,重置连接状态
|
|
isSerialPortOpen = false;
|
|
|
|
if (callback != null) {
|
|
mainHandler.post(() -> callback.onError(errorMsg));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 设置超时处理
|
|
* @param callback 结果回调
|
|
*/
|
|
private void setupTimeout(PeopleNumCallback callback) {
|
|
cancelTimeout(); // 先取消之前的超时任务
|
|
|
|
long startTime = System.currentTimeMillis();
|
|
LogManager.logInfo(TAG, "设置485查询超时处理,超时时间: " + (TIMEOUT_485_MILLIS / 1000) + "秒");
|
|
|
|
timeoutRunnable = () -> {
|
|
long elapsedTime = System.currentTimeMillis() - startTime;
|
|
String errorMsg = "485人数查询超时(等待时间: " + elapsedTime + "ms)";
|
|
LogManager.logError(TAG, errorMsg);
|
|
LogManager.logError(TAG, "485超时详情 - 串口状态: " + (isSerialPortReallyOpen() ? "已打开" : "未打开") +
|
|
", 内部连接状态: " + isSerialPortOpen +
|
|
", 最后成功时间: " + (lastSuccessTime > 0 ?
|
|
new java.text.SimpleDateFormat("HH:mm:ss.SSS").format(new java.util.Date(lastSuccessTime)) : "无"));
|
|
|
|
// 超时可能是连接问题,重置连接状态
|
|
isSerialPortOpen = false;
|
|
|
|
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, "15:45:22.734 Ox485 D 解析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串口状态(懒加载模式)");
|
|
String status = get485Status();
|
|
LogManager.logInfo(TAG, status);
|
|
}
|
|
|
|
/**
|
|
* 发送自定义485 HEX命令
|
|
* @param hexCommand HEX命令字符串
|
|
*/
|
|
public void send485HexCommand(String hexCommand) {
|
|
LogManager.logInfo(TAG, "发送485 HEX命令: " + hexCommand);
|
|
try {
|
|
if (!isSerialPortOpen) {
|
|
LogManager.logWarning(TAG, "串口未打开,无法发送命令");
|
|
return;
|
|
}
|
|
|
|
byte[] hexBytes = hexStringToByteArray(hexCommand);
|
|
serialPortManager.sendBytes(hexBytes);
|
|
LogManager.logInfo(TAG, "485命令发送完成");
|
|
} catch (Exception e) {
|
|
LogManager.logError(TAG, "发送485命令异常: " + e.getMessage(), e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 485连接诊断方法
|
|
* 用于排查485通信问题
|
|
*/
|
|
public void diagnose485Connection() {
|
|
LogManager.logInfo(TAG, "开始485连接诊断");
|
|
|
|
// 1. 检查串口设备文件
|
|
File serialPortFile = new File(DEFAULT_SERIAL_PORT_PATH);
|
|
LogManager.logInfo(TAG, "诊断1 - 串口设备文件: " + DEFAULT_SERIAL_PORT_PATH);
|
|
LogManager.logInfo(TAG, " - 文件存在: " + serialPortFile.exists());
|
|
if (serialPortFile.exists()) {
|
|
LogManager.logInfo(TAG, " - 可读权限: " + serialPortFile.canRead());
|
|
LogManager.logInfo(TAG, " - 可写权限: " + serialPortFile.canWrite());
|
|
LogManager.logInfo(TAG, " - 文件大小: " + serialPortFile.length());
|
|
}
|
|
|
|
// 2. 检查串口管理器状态
|
|
LogManager.logInfo(TAG, "诊断2 - 串口管理器状态:");
|
|
LogManager.logInfo(TAG, " - 串口是否打开: " + (isSerialPortReallyOpen() ? "已打开" : "未打开"));
|
|
LogManager.logInfo(TAG, " - 内部连接状态: " + isSerialPortOpen);
|
|
LogManager.logInfo(TAG, " - 是否连接中: " + isConnecting);
|
|
|
|
// 3. 检查485模式开关
|
|
LogManager.logInfo(TAG, "诊断3 - 485配置:");
|
|
LogManager.logInfo(TAG, " - 485模式开关: " + gateCamera485OxOn);
|
|
LogManager.logInfo(TAG, " - 串口路径: " + DEFAULT_SERIAL_PORT_PATH);
|
|
LogManager.logInfo(TAG, " - 波特率: " + DEFAULT_BAUD_RATE);
|
|
LogManager.logInfo(TAG, " - 停止位: " + DEFAULT_STOP_BITS);
|
|
LogManager.logInfo(TAG, " - 查询命令: " + HEX_COMMAND_GET_PEOPLE_NUM);
|
|
|
|
// 4. 检查连接历史
|
|
LogManager.logInfo(TAG, "诊断4 - 连接历史:");
|
|
String lastSuccessStr = lastSuccessTime > 0 ?
|
|
new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new java.util.Date(lastSuccessTime)) :
|
|
"无";
|
|
LogManager.logInfo(TAG, " - 最后成功时间: " + lastSuccessStr);
|
|
LogManager.logInfo(TAG, " - 当前重试次数: " + currentRetryCount);
|
|
LogManager.logInfo(TAG, " - 最大重试次数: " + MAX_RETRY_COUNT);
|
|
|
|
// 5. 尝试重新建立连接进行测试
|
|
LogManager.logInfo(TAG, "诊断5 - 连接测试:");
|
|
if (gateCamera485OxOn) {
|
|
LogManager.logInfo(TAG, " - 485模式已启用,尝试测试连接...");
|
|
|
|
// 强制关闭现有连接
|
|
if (isSerialPortOpen) {
|
|
LogManager.logInfo(TAG, " - 关闭现有连接进行重新测试");
|
|
close485SerialPort();
|
|
}
|
|
|
|
// 尝试重新连接并发送测试命令
|
|
sendHex485ForPeopleNum(new PeopleNumCallback() {
|
|
@Override
|
|
public void onSuccess(int peopleNum) {
|
|
LogManager.logInfo(TAG, "诊断测试成功 - 获取到人数: " + peopleNum);
|
|
}
|
|
|
|
@Override
|
|
public void onError(String errorMessage) {
|
|
LogManager.logError(TAG, "诊断测试失败: " + errorMessage);
|
|
}
|
|
});
|
|
} else {
|
|
LogManager.logWarning(TAG, " - 485模式未启用,无法进行连接测试");
|
|
}
|
|
|
|
LogManager.logInfo(TAG, "485连接诊断完成");
|
|
}
|
|
|
|
/**
|
|
* 获取485串口状态(懒加载连接管理模式)
|
|
* @return 串口状态描述
|
|
*/
|
|
public String get485Status() {
|
|
String connectionStatus = isSerialPortOpen ? "已连接" : (
|
|
isConnecting ? "连接中" : "未连接"
|
|
);
|
|
|
|
String lastSuccessStr = lastSuccessTime > 0 ?
|
|
new java.text.SimpleDateFormat("HH:mm:ss.SSS").format(new java.util.Date(lastSuccessTime)) :
|
|
"无";
|
|
|
|
String status = "串口状态: " + connectionStatus +
|
|
", 路径: " + DEFAULT_SERIAL_PORT_PATH +
|
|
", 波特率: " + DEFAULT_BAUD_RATE +
|
|
", 485模式: " + (gateCamera485OxOn ? "已启用" : "未启用") +
|
|
", 最后成功时间: " + lastSuccessStr +
|
|
", 重试次数: " + currentRetryCount;
|
|
|
|
LogManager.logInfo(TAG, status);
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* 关闭485串口
|
|
*/
|
|
public void close485SerialPort() {
|
|
LogManager.logInfo(TAG, "关闭485串口");
|
|
try {
|
|
cancelTimeout();
|
|
|
|
if (serialPortManager != null) {
|
|
serialPortManager.closeSerialPort();
|
|
LogManager.logInfo(TAG, "485串口已关闭");
|
|
}
|
|
|
|
// 重置连接状态
|
|
isSerialPortOpen = false;
|
|
isConnecting = false;
|
|
lastSuccessTime = 0;
|
|
currentRetryCount = 0;
|
|
|
|
} catch (Exception e) {
|
|
LogManager.logError(TAG, "关闭485串口异常: " + e.getMessage(), e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 释放资源
|
|
*/
|
|
public void release() {
|
|
LogManager.logInfo(TAG, "释放Ox485资源");
|
|
|
|
// 停止连接检查
|
|
stopConnectionCheck();
|
|
|
|
// 关闭串口
|
|
close485SerialPort();
|
|
}
|
|
|
|
// ========== 懒加载连接管理相关方法 ==========
|
|
|
|
/**
|
|
* 初始化连接检查任务
|
|
*/
|
|
private void initConnectionCheckTask() {
|
|
connectionCheckRunnable = new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
LogManager.logDebug(TAG, "定时检查485连接状态");
|
|
|
|
// 检查最后一次成功时间,如果超过一定时间没有成功通信,则认为连接异常
|
|
long currentTime = System.currentTimeMillis();
|
|
if (isSerialPortOpen && lastSuccessTime > 0 &&
|
|
(currentTime - lastSuccessTime) > CONNECTION_CHECK_INTERVAL * 2) {
|
|
LogManager.logWarning(TAG, "检测到485连接异常,最后成功时间: " +
|
|
new java.text.SimpleDateFormat("HH:mm:ss.SSS").format(new java.util.Date(lastSuccessTime)));
|
|
|
|
// 重置连接状态
|
|
isSerialPortOpen = false;
|
|
|
|
// 尝试重连
|
|
if (gateCamera485OxOn) {
|
|
LogManager.logInfo(TAG, "尝试重新建立485连接");
|
|
retryConnection();
|
|
}
|
|
}
|
|
|
|
// 安排下次检查
|
|
connectionCheckHandler.postDelayed(this, CONNECTION_CHECK_INTERVAL);
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 启动连接检查
|
|
*/
|
|
private void startConnectionCheck() {
|
|
LogManager.logInfo(TAG, "启动485连接定时检查,检查间隔: " + (CONNECTION_CHECK_INTERVAL / 1000) + "秒");
|
|
connectionCheckHandler.post(connectionCheckRunnable);
|
|
}
|
|
|
|
/**
|
|
* 停止连接检查
|
|
*/
|
|
private void stopConnectionCheck() {
|
|
if (connectionCheckHandler != null && connectionCheckRunnable != null) {
|
|
connectionCheckHandler.removeCallbacks(connectionCheckRunnable);
|
|
LogManager.logInfo(TAG, "485连接定时检查已停止");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 确保连接可用(懒加载核心逻辑)
|
|
* @param callback 连接结果回调
|
|
*/
|
|
private void ensureConnection(ConnectionCallback callback) {
|
|
// 如果正在连接中,等待连接完成
|
|
if (isConnecting) {
|
|
LogManager.logInfo(TAG, "连接正在进行中,等待连接完成...");
|
|
// 简单等待机制,实际项目中可以优化为队列机制
|
|
mainHandler.postDelayed(() -> ensureConnection(callback), 500);
|
|
return;
|
|
}
|
|
|
|
// 如果已经连接,直接返回
|
|
if (isSerialPortOpen) {
|
|
LogManager.logDebug(TAG, "485连接已存在,直接复用");
|
|
callback.onConnected();
|
|
return;
|
|
}
|
|
|
|
// 需要建立新连接
|
|
LogManager.logInfo(TAG, "首次使用或连接已断开,建立485连接");
|
|
createConnection(callback);
|
|
}
|
|
|
|
/**
|
|
* 建立485连接
|
|
* @param callback 连接结果回调
|
|
*/
|
|
private void createConnection(ConnectionCallback callback) {
|
|
isConnecting = true;
|
|
currentRetryCount = 0;
|
|
|
|
try {
|
|
LogManager.logInfo(TAG, "开始建立485连接 - 路径: " + DEFAULT_SERIAL_PORT_PATH + ", 波特率: " + DEFAULT_BAUD_RATE);
|
|
|
|
// 检查串口文件是否存在
|
|
File serialPortFile = new File(DEFAULT_SERIAL_PORT_PATH);
|
|
if (!serialPortFile.exists()) {
|
|
isConnecting = false;
|
|
String errorMsg = "485串口设备文件不存在: " + DEFAULT_SERIAL_PORT_PATH;
|
|
LogManager.logError(TAG, errorMsg);
|
|
callback.onError(errorMsg);
|
|
return;
|
|
}
|
|
|
|
LogManager.logInfo(TAG, "485串口设备文件存在,文件权限: 可读=" + serialPortFile.canRead() + ", 可写=" + serialPortFile.canWrite());
|
|
|
|
// 配置串口参数
|
|
boolean openResult = serialPortManager.openSerialPort(serialPortFile, DEFAULT_BAUD_RATE);
|
|
|
|
LogManager.logInfo(TAG, "485串口打开结果: " + openResult);
|
|
|
|
if (!openResult) {
|
|
isConnecting = false;
|
|
String errorMsg = "打开485串口失败: " + DEFAULT_SERIAL_PORT_PATH + ",可能原因:设备不存在、权限不足或设备被占用";
|
|
LogManager.logError(TAG, errorMsg);
|
|
callback.onError(errorMsg);
|
|
return;
|
|
}
|
|
|
|
// 验证串口是否真正打开
|
|
if (!isSerialPortReallyOpen()) {
|
|
isConnecting = false;
|
|
String errorMsg = "485串口打开后状态检查失败,串口可能未正确初始化";
|
|
LogManager.logError(TAG, errorMsg);
|
|
callback.onError(errorMsg);
|
|
return;
|
|
}
|
|
|
|
// 启动读写线程
|
|
serialPortManager.startReadThread(485);
|
|
serialPortManager.startSendThread();
|
|
|
|
// 连接成功
|
|
isSerialPortOpen = true;
|
|
isConnecting = false;
|
|
lastSuccessTime = System.currentTimeMillis();
|
|
currentRetryCount = 0;
|
|
|
|
LogManager.logInfo(TAG, "485连接建立成功,进入懒加载复用模式,最后成功时间: " +
|
|
new java.text.SimpleDateFormat("HH:mm:ss.SSS").format(new java.util.Date(lastSuccessTime)));
|
|
callback.onConnected();
|
|
|
|
} catch (Exception e) {
|
|
isConnecting = false;
|
|
isSerialPortOpen = false;
|
|
String errorMsg = "建立485连接异常: " + e.getMessage();
|
|
LogManager.logError(TAG, errorMsg, e);
|
|
callback.onError(errorMsg);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 重试连接
|
|
*/
|
|
private void retryConnection() {
|
|
if (currentRetryCount >= MAX_RETRY_COUNT) {
|
|
LogManager.logError(TAG, "485连接重试次数超限,停止重试");
|
|
return;
|
|
}
|
|
|
|
currentRetryCount++;
|
|
LogManager.logInfo(TAG, "485连接重试第 " + currentRetryCount + " 次");
|
|
|
|
// 延迟重试
|
|
mainHandler.postDelayed(() -> {
|
|
createConnection(new ConnectionCallback() {
|
|
@Override
|
|
public void onConnected() {
|
|
LogManager.logInfo(TAG, "485连接重试成功");
|
|
}
|
|
|
|
@Override
|
|
public void onError(String errorMessage) {
|
|
LogManager.logWarning(TAG, "485连接重试失败: " + errorMessage);
|
|
// 继续重试
|
|
retryConnection();
|
|
}
|
|
});
|
|
}, RETRY_DELAY);
|
|
}
|
|
|
|
/**
|
|
* 标记成功通信(用于连接健康检查)
|
|
*/
|
|
private void markSuccessfulCommunication() {
|
|
lastSuccessTime = System.currentTimeMillis();
|
|
currentRetryCount = 0; // 重置重试次数
|
|
LogManager.logDebug(TAG, "485通信成功,更新最后成功时间");
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
/**
|
|
* 检查是否为正常的485响应数据包
|
|
* 只处理以[15, 3, 4, 0]开头的正常响应数据包
|
|
* @param bytes 接收到的字节数组
|
|
* @return true表示是正常响应数据包,false表示是其他数据包
|
|
*/
|
|
private boolean isNormalResponsePacket(byte[] bytes) {
|
|
// 数据校验
|
|
if (bytes == null || bytes.length < 4) {
|
|
return false;
|
|
}
|
|
|
|
// 检查是否以[15, 3, 4, 0]开头
|
|
// 15: 0x0F, 3: 0x03, 4: 0x04, 0: 0x00
|
|
return (bytes[0] == 15 && bytes[1] == 3 && bytes[2] == 4 && bytes[3] == 0);
|
|
}
|
|
|
|
/**
|
|
* 检查串口是否真正打开
|
|
* 由于 SerialPortManager 没有 isOpened() 方法,
|
|
* 我们通过检查内部状态变量来判断
|
|
* @return true表示串口已打开
|
|
*/
|
|
private boolean isSerialPortReallyOpen() {
|
|
return isSerialPortOpen && serialPortManager != null;
|
|
}
|
|
}
|