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 人数结果 */ public CompletableFuture sendHex485ForPeopleNumAsync() { LogManager.logInfo(TAG, "异步获取摄像头人数"); CompletableFuture 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; } }