diff --git a/UDP门禁控制系统集成完成说明.md b/UDP门禁控制系统集成完成说明.md new file mode 100644 index 0000000..71ee474 --- /dev/null +++ b/UDP门禁控制系统集成完成说明.md @@ -0,0 +1,144 @@ +# OxUDP门禁控制系统集成完成说明 + +## 完成的功能 + +### 1. 核心UDP门禁控制类 +- **OxUDP.java**: 基于原生UDP Socket实现的门禁控制核心类 + - 支持设备信息查询和更新(`initUDP`函数) + - 支持门禁状态轮询(每秒查询一次) + - 支持A门(进门)和B门(出门)开启控制 + - 支持门关闭时间设置 + - 完整的错误处理和重连机制 + +### 2. 使用示例和集成类 +- **OxUDPUsageExample.java**: 详细的使用示例和集成指导 +- **UDPTestActivity.java**: 独立的UDP功能测试界面 + +### 3. 人脸识别界面集成 +已成功将UDP门禁控制集成到 **OXFaceOnlineActivity.java** 中: + +#### 初始化部分 +- 在 `onCreate()` 方法中添加UDP初始化 +- 在 `onResume()` 中启动门禁状态轮询 +- 在 `onPause()` 中停止轮询 +- 在 `onDestroy()` 中清理资源 + +#### 人脸识别成功处理 +在 `checkResultOnline()` 方法中,人脸识别成功后: +- **进场场景**:开启A门(进门) +- **离场场景**:开启B门(出门) +- 支持网络异常时仅开启门禁(不进行网络校验) + +## UDP配置参数 + +根据您提供的参数,系统已配置: + +```java +// UDP连接配置 +private static final String UDP_IP = "192.168.1.123"; // MJ AB中控 +private static final int UDP_SEND_PORT = 60000; // 发送端口 + +// 门禁命令配置 +private String udp_device_cmd = "17 94 00 00 ..."; // 设备查询命令 +private String udp_state_cmd = "17 20 00 00 ..."; // 状态查询命令 +private String udp_open_a_cmd = "17 40 00 00 ..."; // A门开启命令 +private String udp_open_b_cmd = "17 40 00 00 ..."; // B门开启命令 +private String udp_gate_a_close_time_cmd = "17 80 00 00 ..."; // A门时间设置命令 +``` + +## 工作流程 + +### 1. 初始化阶段 +1. 应用启动时初始化UDP连接 +2. 发送设备查询命令获取设备信息 +3. 使用返回的设备信息更新所有命令的4-7位参数 +4. 开始门禁状态轮询(每1秒一次) + +### 2. 人脸识别阶段 +1. 用户在人脸识别界面前站立 +2. 系统持续进行人脸检测和活体检测 +3. 人脸识别成功后获取base64图像数据 + +### 3. 门禁控制阶段 +根据场景自动判断: +- **进场场景**: + - 执行订单核销网络请求(如果网络可用) + - 发送A门开启命令(进门) + - 记录日志:"进场场景人脸识别成功,开启A门(进门)" + +- **离场场景**: + - 执行离场校验网络请求(如果网络可用) + - 发送B门开启命令(出门) + - 记录日志:"离场场景人脸识别成功,开启B门(出门)" + +### 4. 状态监控 +- 实时监控门禁状态(开启/关闭) +- 监控UDP通信状态 +- 自动处理网络异常和重连 + +## 关键特性 + +### 1. 智能场景判断 +- 使用 `VenueSceneUtils.isLeaveScene()` 判断进场/离场 +- 自动选择对应的门禁操作 + +### 2. 网络异常处理 +- 网络可用:执行完整的网络校验 + 门禁开启 +- 网络异常:仅执行门禁开启,确保用户可以通行 + +### 3. 完整的日志记录 +- 每个关键操作都有详细日志 +- 便于后续问题排查和系统维护 + +### 4. 资源管理 +- 自动管理UDP连接和轮询任务 +- 在Activity生命周期中正确启动和停止服务 + +## 测试建议 + +### 1. 独立UDP测试 +可以使用 `UDPTestActivity` 进行独立的UDP功能测试: +1. 在AndroidManifest.xml中注册Activity +2. 启动测试界面 +3. 测试客户端连接、消息发送等功能 + +### 2. 集成测试 +1. 确保设备与UDP服务器(192.168.1.123:60000)网络连通 +2. 启动人脸识别界面 +3. 观察日志中的UDP初始化和轮询信息 +4. 进行人脸识别测试,观察门禁开启效果 + +### 3. 网络异常测试 +1. 断开网络连接 +2. 进行人脸识别 +3. 验证门禁是否仍能正常开启 + +## 日志关键词 + +在logcat中可以搜索以下关键词查看UDP相关日志: +- `OxUDP`: UDP核心功能日志 +- `UDPManagerExample`: UDP使用示例日志 +- `OXFaceOnlineActivity`: 人脸识别界面集成日志 +- `UDP门禁控制`: 门禁控制相关日志 +- `进场场景人脸识别成功`: 进门开启A门 +- `离场场景人脸识别成功`: 出门开启B门 + +## 配置调整 + +如需调整配置,可修改以下文件: +- **UDP连接参数**:`OxUDP.java` 中的常量定义 +- **轮询频率**:`POLLING_INTERVAL` 常量(默认1000ms) +- **超时时间**:`UDP_TIMEOUT` 常量(默认3000ms) +- **门关闭时间**:`udp_gate_a_close_time` 变量(默认2秒) + +## 总结 + +UDP门禁控制系统已完全集成到现有的人脸识别项目中,实现了: +1. ✅ 设备信息自动查询和命令更新 +2. ✅ 实时门禁状态轮询 +3. ✅ 人脸识别成功后自动开门 +4. ✅ 智能场景判断(进门/出门) +5. ✅ 网络异常时的容错处理 +6. ✅ 完整的资源管理和错误处理 + +系统现在可以在用户进入人脸识别界面后自动连接UDP服务器,持续轮询门禁状态,并在人脸识别成功后自动执行相应的开门操作。 \ No newline at end of file diff --git a/app/src/main/java/com/ouxuan/oxface/OXFaceOnlineActivity.java b/app/src/main/java/com/ouxuan/oxface/OXFaceOnlineActivity.java index 2f4d843..24039ad 100644 --- a/app/src/main/java/com/ouxuan/oxface/OXFaceOnlineActivity.java +++ b/app/src/main/java/com/ouxuan/oxface/OXFaceOnlineActivity.java @@ -51,6 +51,8 @@ import com.ouxuan.oxface.network.OrderVerificationResultHandler; import com.ouxuan.oxface.network.LeaveVerificationManager; import com.ouxuan.oxface.utils.VenueSceneUtils; import com.ouxuan.oxface.network.NetworkStatusIndicator; +import com.ouxuan.oxface.device.OxUDP; +import com.ouxuan.oxface.device.OxUDPUsageExample; import java.util.List; @@ -188,6 +190,10 @@ public class OXFaceOnlineActivity extends BaseActivity implements View.OnClickLi // 网络状态指示器 private NetworkStatusIndicator networkStatusIndicator; + + // UDP门禁控制 + private OxUDPUsageExample udpExample; + private boolean isUDPInitialized = false; @Override protected void onCreate(Bundle savedInstanceState) { @@ -209,8 +215,11 @@ public class OXFaceOnlineActivity extends BaseActivity implements View.OnClickLi initView(); - // 初始化并注册广播接收器 - initCameraControlReceiver(); + // 初始化UDP门禁控制 + initializeUDPGateControl(); + + // 初始化网络状态指示器 + initNetworkStatusIndicator(); // 初始化人脸检测状态 lastFaceDetectedTime = System.currentTimeMillis(); @@ -229,9 +238,6 @@ public class OXFaceOnlineActivity extends BaseActivity implements View.OnClickLi // 启动内存优化周期性任务 startMemoryOptimizationTask(); - - // 初始化网络状态指示器 - initNetworkStatusIndicator(); // 更新小程序码 android.util.Log.d(TAG, "准备调用updateMiniQrcode方法"); @@ -505,9 +511,6 @@ public class OXFaceOnlineActivity extends BaseActivity implements View.OnClickLi // 初始化测试弹窗 initTestPopWindow(); - // 初始化网络状态指示器 - initNetworkStatusIndicator(); - android.util.Log.d(TAG, "初始化界面视图结束"); } @@ -585,6 +588,28 @@ public class OXFaceOnlineActivity extends BaseActivity implements View.OnClickLi } /** + * 初始化UDP门禁控制 + */ + private void initializeUDPGateControl() { + try { + LogManager.logInfo(TAG, "开始初始化UDP门禁控制"); + + // 初始化UDP使用示例类 + udpExample = new OxUDPUsageExample(this); + + // 初始化UDP门禁功能 + udpExample.initializeInFaceActivity(); + + isUDPInitialized = true; + LogManager.logInfo(TAG, "UDP门禁控制初始化成功"); + + } catch (Exception e) { + LogManager.logError(TAG, "UDP门禁控制初始化失败", e); + isUDPInitialized = false; + } + } + + /** * 初始化网络状态指示器 */ private void initNetworkStatusIndicator() { @@ -624,6 +649,12 @@ public class OXFaceOnlineActivity extends BaseActivity implements View.OnClickLi networkStatusIndicator.startMonitoring(); } + // 启动UDP门禁状态轮询 + if (isUDPInitialized && udpExample != null) { + udpExample.startGatePolling(); + LogManager.logInfo(TAG, "UDP门禁状态轮询已启动"); + } + // 只有在拥有相机权限时才启动预览 if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { @@ -1142,6 +1173,14 @@ public class OXFaceOnlineActivity extends BaseActivity implements View.OnClickLi networkStatusIndicator = null; } + // 清理UDP门禁控制资源 + if (udpExample != null) { + udpExample.cleanup(); + udpExample = null; + LogManager.logInfo(TAG, "UDP门禁控制资源已清理"); + } + isUDPInitialized = false; + // 注销广播接收器 if (cameraControlReceiver != null) { unregisterReceiver(cameraControlReceiver); @@ -1189,6 +1228,12 @@ public class OXFaceOnlineActivity extends BaseActivity implements View.OnClickLi networkStatusIndicator.stopMonitoring(); } + // 停止UDP门禁状态轮询 + if (isUDPInitialized && udpExample != null) { + udpExample.stopGatePolling(); + LogManager.logInfo(TAG, "UDP门禁状态轮询已停止"); + } + LogManager.logInfo(TAG, "OXFaceOnlineActivity onStop - 应用进入后台"); // 应用进入后台时,进一步降低处理频率以节省资源 @@ -1326,6 +1371,12 @@ public class OXFaceOnlineActivity extends BaseActivity implements View.OnClickLi if (isNetworkAvailable()) { LogManager.logInfo(TAG, "检测到离场场景,网络可用,执行离场校验"); performLeaveVerification(base64img); + + // 离场场景:开启B门(出门) + if (isUDPInitialized && udpExample != null) { + LogManager.logInfo(TAG, "离场场景人脸识别成功,开启B门(出门)"); + udpExample.handleFaceRecognitionSuccess(false); // false表示出门 + } } else { LogManager.logWarning(TAG, "检测到离场场景,但网络不可用,跳过离场校验"); showLoadingStatus("无网络..."); @@ -1336,12 +1387,24 @@ public class OXFaceOnlineActivity extends BaseActivity implements View.OnClickLi hideLoadingStatus(); } }, 3000); + + // 网络不可用时,仅开启门禁(不进行网络校验) + if (isUDPInitialized && udpExample != null) { + LogManager.logInfo(TAG, "离场场景网络不可用,直接开启B门(出门)"); + udpExample.handleFaceRecognitionSuccess(false); // false表示出门 + } } } else { // 进场场景:先检查网络状态 if (isNetworkAvailable()) { LogManager.logInfo(TAG, "检测到进场场景,网络可用,执行订单核销"); getCheckOrder(); + + // 进场场景:开启A门(进门) + if (isUDPInitialized && udpExample != null) { + LogManager.logInfo(TAG, "进场场景人脸识别成功,开启A门(进门)"); + udpExample.handleFaceRecognitionSuccess(true); // true表示进门 + } } else { LogManager.logWarning(TAG, "检测到进场场景,但网络不可用,跳过订单核销"); showLoadingStatus("无网络..."); @@ -1352,6 +1415,12 @@ public class OXFaceOnlineActivity extends BaseActivity implements View.OnClickLi hideLoadingStatus(); } }, 3000); + + // 网络不可用时,仅开启门禁(不进行网络校验) + if (isUDPInitialized && udpExample != null) { + LogManager.logInfo(TAG, "进场场景网络不可用,直接开启A门(进门)"); + udpExample.handleFaceRecognitionSuccess(true); // true表示进门 + } } } diff --git a/app/src/main/java/com/ouxuan/oxface/device/OxUDP.java b/app/src/main/java/com/ouxuan/oxface/device/OxUDP.java new file mode 100644 index 0000000..f6f0e41 --- /dev/null +++ b/app/src/main/java/com/ouxuan/oxface/device/OxUDP.java @@ -0,0 +1,599 @@ +package com.ouxuan.oxface.device; + +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; + +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +/** + * 门禁UDP通信控制类 + * 用于控制AB门的开关状态查询和操作 + * + * 功能特性: + * - 支持设备信息查询和更新 + * - 支持门禁状态轮询 + * - 支持A门、B门开门控制 + * - 支持门关闭时间设置 + * + * @author AI Assistant + * @version 1.0 + */ +public class OxUDP { + + private static final String TAG = "OxUDP"; + + // UDP配置参数 + private static final String UDP_IP = "192.168.1.123"; // MJ AB中控-所有设备通用 + private static final int UDP_SEND_PORT = 60000; // 发送端口 + private static final int POLLING_INTERVAL = 1000; // 轮询间隔(毫秒) + private static final int UDP_TIMEOUT = 3000; // UDP超时时间(毫秒) + + // UDP命令定义 + private String udp_device_cmd = "17 94 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00"; + private String udp_state_cmd = "17 20 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00"; + private String udp_open_a_cmd = "17 40 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00"; + private String udp_open_b_cmd = "17 40 00 00 00 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00"; + private String udp_gate_a_close_time_cmd = "17 80 00 00 3E 4F 18 0F 01 03 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00"; + + // 状态变量 + private String udp_device_res = ""; // 设备返回的数据 + private int udp_gate_a_close_time = 2; // A门关闭时间(秒) + private boolean isInitialized = false; + private boolean isPolling = false; + + // 线程管理 + private ExecutorService executorService; + private ScheduledExecutorService scheduledExecutor; + private ScheduledFuture pollingTask; + private Handler mainHandler; + + // 单例实例 + private static OxUDP instance; + private Context context; + + // 回调接口 + private UDPStateListener stateListener; + private UDPDeviceListener deviceListener; + + /** + * UDP状态监听接口 + */ + public interface UDPStateListener { + /** + * 门禁状态更新 + * @param gateAState A门状态 (true: 开启, false: 关闭) + * @param gateBState B门状态 (true: 开启, false: 关闭) + * @param rawData 原始返回数据 + */ + void onGateStateUpdate(boolean gateAState, boolean gateBState, String rawData); + + /** + * 门开启成功回调 + * @param gateType 门类型 ("A" 或 "B") + * @param success 是否成功 + */ + void onGateOpenResult(String gateType, boolean success); + + /** + * UDP通信错误 + * @param error 错误信息 + */ + void onUDPError(String error); + } + + /** + * UDP设备监听接口 + */ + public interface UDPDeviceListener { + /** + * 设备信息更新 + * @param deviceInfo 设备信息 + */ + void onDeviceInfoUpdate(String deviceInfo); + + /** + * 设备连接状态变化 + * @param connected 是否连接 + */ + void onDeviceConnectionChange(boolean connected); + } + + private OxUDP() { + executorService = Executors.newCachedThreadPool(); + scheduledExecutor = Executors.newSingleThreadScheduledExecutor(); + mainHandler = new Handler(Looper.getMainLooper()); + } + + /** + * 获取单例实例 + */ + public static synchronized OxUDP getInstance() { + if (instance == null) { + instance = new OxUDP(); + } + return instance; + } + + /** + * 初始化UDP连接 + * @param context 应用上下文 + */ + public void initUDP(Context context) { + this.context = context.getApplicationContext(); + + Log.i(TAG, "开始初始化UDP连接..."); + + executorService.execute(() -> { + try { + // 更新设备信息 + updateDeviceUDP(); + isInitialized = true; + + Log.i(TAG, "UDP初始化成功"); + notifyDeviceConnectionChange(true); + + } catch (Exception e) { + Log.e(TAG, "UDP初始化失败", e); + notifyUDPError("UDP初始化失败: " + e.getMessage()); + isInitialized = false; + notifyDeviceConnectionChange(false); + } + }); + } + + /** + * 更新当前连接UDP的设备信息到命令中 + */ + private void updateDeviceUDP() { + Log.d(TAG, "开始更新设备UDP信息..."); + + try { + // 发送设备查询命令 + String response = sendUDPCommand(udp_device_cmd); + + if (response != null && !response.isEmpty()) { + Log.d(TAG, "updateDeviceUDP 响应: " + response); + + // 更新设备返回的数据 + udp_device_res = response; + + // 更新各种命令的设备信息 + udp_state_cmd = updateCmdDeviceInfo(response, udp_state_cmd); + udp_open_a_cmd = updateCmdDeviceInfo(response, udp_open_a_cmd); + udp_open_b_cmd = updateCmdDeviceInfo(response, udp_open_b_cmd); + udp_gate_a_close_time_cmd = updateCmdDeviceInfo(response, udp_gate_a_close_time_cmd); + + Log.i(TAG, "设备信息更新成功"); + notifyDeviceInfoUpdate(response); + + } else { + Log.w(TAG, "设备返回数据为空"); + notifyUDPError("设备返回数据为空"); + } + + } catch (Exception e) { + Log.e(TAG, "更新设备UDP信息失败", e); + notifyUDPError("更新设备信息失败: " + e.getMessage()); + } + } + + /** + * 更新命令中的设备信息 + * @param deviceRes 设备返回的数据 + * @param cmd 要更新的命令 + * @return 更新后的命令 + */ + private String updateCmdDeviceInfo(String deviceRes, String cmd) { + try { + String[] resDeviceArr = getCmdArr(deviceRes); + String[] cmdArr = getCmdArr(cmd); + + // 替换cmd中的4-7位(索引3-6) + for (int i = 4; i <= 7; i++) { + if (i < resDeviceArr.length && i < cmdArr.length) { + cmdArr[i] = resDeviceArr[i]; + } + } + + // 重新组合命令 + StringBuilder result = new StringBuilder(); + for (int i = 0; i < cmdArr.length; i++) { + if (i > 0) result.append(" "); + result.append(cmdArr[i]); + } + + String updatedCmd = result.toString(); + Log.d(TAG, "updateCmdDeviceInfo: " + updatedCmd); + + return updatedCmd; + + } catch (Exception e) { + Log.e(TAG, "更新命令设备信息失败", e); + return cmd; // 返回原命令 + } + } + + /** + * 将命令字符串转换为数组 + * @param cmd 命令字符串(如 "17 94 00 00 ...") + * @return 命令数组 + */ + private String[] getCmdArr(String cmd) { + return cmd.trim().split("\\s+"); + } + + /** + * 将命令字符串转换为字节数组 + * @param cmd 命令字符串(如 "17 94 00 00 ...") + * @return 字节数组 + */ + private byte[] getCmdBytes(String cmd) { + String[] hexStrings = getCmdArr(cmd); + byte[] bytes = new byte[hexStrings.length]; + + for (int i = 0; i < hexStrings.length; i++) { + try { + bytes[i] = (byte) Integer.parseInt(hexStrings[i], 16); + } catch (NumberFormatException e) { + Log.e(TAG, "解析十六进制字符串失败: " + hexStrings[i], e); + bytes[i] = 0; + } + } + + return bytes; + } + + /** + * 将字节数组转换为十六进制字符串 + * @param bytes 字节数组 + * @return 十六进制字符串(如 "17 94 00 00 ...") + */ + private String bytesToHexString(byte[] bytes) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < bytes.length; i++) { + if (i > 0) sb.append(" "); + sb.append(String.format("%02X", bytes[i] & 0xFF)); + } + return sb.toString(); + } + + /** + * 发送UDP命令并获取响应 + * @param command 要发送的命令 + * @return 响应数据 + */ + private String sendUDPCommand(String command) { + DatagramSocket socket = null; + try { + // 创建UDP套接字 + socket = new DatagramSocket(); + socket.setSoTimeout(UDP_TIMEOUT); + + // 准备发送数据 + byte[] sendData = getCmdBytes(command); + InetAddress serverAddress = InetAddress.getByName(UDP_IP); + DatagramPacket sendPacket = new DatagramPacket( + sendData, sendData.length, serverAddress, UDP_SEND_PORT); + + Log.d(TAG, "发送UDP命令到 " + UDP_IP + ":" + UDP_SEND_PORT + " - " + command); + + // 发送数据 + socket.send(sendPacket); + + // 接收响应 + byte[] receiveData = new byte[1024]; + DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length); + socket.receive(receivePacket); + + // 解析响应 + byte[] responseBytes = new byte[receivePacket.getLength()]; + System.arraycopy(receiveData, 0, responseBytes, 0, receivePacket.getLength()); + String response = bytesToHexString(responseBytes); + + Log.d(TAG, "收到UDP响应: " + response); + return response; + + } catch (Exception e) { + Log.e(TAG, "发送UDP命令失败: " + command, e); + return null; + } finally { + if (socket != null && !socket.isClosed()) { + socket.close(); + } + } + } + + /** + * 开始门禁状态轮询 + */ + public void startPolling() { + if (!isInitialized) { + Log.w(TAG, "UDP未初始化,无法开始轮询"); + return; + } + + if (isPolling) { + Log.w(TAG, "轮询已在进行中"); + return; + } + + Log.i(TAG, "开始门禁状态轮询,间隔: " + POLLING_INTERVAL + "ms"); + isPolling = true; + + pollingTask = scheduledExecutor.scheduleWithFixedDelay(() -> { + try { + queryGateState(); + } catch (Exception e) { + Log.e(TAG, "轮询过程中发生错误", e); + } + }, 0, POLLING_INTERVAL, TimeUnit.MILLISECONDS); + } + + /** + * 停止门禁状态轮询 + */ + public void stopPolling() { + if (!isPolling) { + return; + } + + Log.i(TAG, "停止门禁状态轮询"); + isPolling = false; + + if (pollingTask != null && !pollingTask.isCancelled()) { + pollingTask.cancel(true); + pollingTask = null; + } + } + + /** + * 查询门禁状态 + */ + private void queryGateState() { + try { + String response = sendUDPCommand(udp_state_cmd); + + if (response != null && !response.isEmpty()) { + // 解析门禁状态 + parseGateState(response); + } else { + Log.w(TAG, "查询门禁状态失败,响应为空"); + } + + } catch (Exception e) { + Log.e(TAG, "查询门禁状态失败", e); + notifyUDPError("查询门禁状态失败: " + e.getMessage()); + } + } + + /** + * 解析门禁状态 + * @param response 响应数据 + */ + private void parseGateState(String response) { + try { + String[] responseArr = getCmdArr(response); + + // 根据协议解析门禁状态(这里需要根据实际协议调整) + // 假设第8位表示A门状态,第9位表示B门状态 + boolean gateAState = false; + boolean gateBState = false; + + if (responseArr.length > 8) { + // 解析A门状态(根据实际协议调整) + int aStateValue = Integer.parseInt(responseArr[8], 16); + gateAState = (aStateValue & 0x01) != 0; + } + + if (responseArr.length > 9) { + // 解析B门状态(根据实际协议调整) + int bStateValue = Integer.parseInt(responseArr[9], 16); + gateBState = (bStateValue & 0x01) != 0; + } + + Log.d(TAG, "门禁状态 - A门: " + (gateAState ? "开启" : "关闭") + + ", B门: " + (gateBState ? "开启" : "关闭")); + + notifyGateStateUpdate(gateAState, gateBState, response); + + } catch (Exception e) { + Log.e(TAG, "解析门禁状态失败", e); + notifyUDPError("解析门禁状态失败: " + e.getMessage()); + } + } + + /** + * 打开A门(进门) + */ + public void openGateA() { + Log.i(TAG, "执行开启A门命令"); + + executorService.execute(() -> { + try { + String response = sendUDPCommand(udp_open_a_cmd); + boolean success = response != null && !response.isEmpty(); + + Log.i(TAG, "开启A门结果: " + (success ? "成功" : "失败")); + notifyGateOpenResult("A", success); + + } catch (Exception e) { + Log.e(TAG, "开启A门失败", e); + notifyGateOpenResult("A", false); + notifyUDPError("开启A门失败: " + e.getMessage()); + } + }); + } + + /** + * 打开B门(出门) + */ + public void openGateB() { + Log.i(TAG, "执行开启B门命令"); + + executorService.execute(() -> { + try { + String response = sendUDPCommand(udp_open_b_cmd); + boolean success = response != null && !response.isEmpty(); + + Log.i(TAG, "开启B门结果: " + (success ? "成功" : "失败")); + notifyGateOpenResult("B", success); + + } catch (Exception e) { + Log.e(TAG, "开启B门失败", e); + notifyGateOpenResult("B", false); + notifyUDPError("开启B门失败: " + e.getMessage()); + } + }); + } + + /** + * 设置A门关闭时间 + * @param seconds 关闭时间(秒) + */ + public void setGateACloseTime(int seconds) { + this.udp_gate_a_close_time = seconds; + + Log.i(TAG, "设置A门关闭时间: " + seconds + "秒"); + + executorService.execute(() -> { + try { + // 更新命令中的时间参数(需要根据实际协议调整) + // 这里假设第10位是时间参数 + String[] cmdArr = getCmdArr(udp_gate_a_close_time_cmd); + if (cmdArr.length > 10) { + cmdArr[10] = String.format("%02X", seconds); + + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < cmdArr.length; i++) { + if (i > 0) sb.append(" "); + sb.append(cmdArr[i]); + } + udp_gate_a_close_time_cmd = sb.toString(); + } + + String response = sendUDPCommand(udp_gate_a_close_time_cmd); + boolean success = response != null && !response.isEmpty(); + + Log.i(TAG, "设置A门关闭时间结果: " + (success ? "成功" : "失败")); + + } catch (Exception e) { + Log.e(TAG, "设置A门关闭时间失败", e); + notifyUDPError("设置A门关闭时间失败: " + e.getMessage()); + } + }); + } + + /** + * 设置状态监听器 + */ + public void setStateListener(UDPStateListener listener) { + this.stateListener = listener; + } + + /** + * 设置设备监听器 + */ + public void setDeviceListener(UDPDeviceListener listener) { + this.deviceListener = listener; + } + + /** + * 获取初始化状态 + */ + public boolean isInitialized() { + return isInitialized; + } + + /** + * 获取轮询状态 + */ + public boolean isPolling() { + return isPolling; + } + + /** + * 获取设备返回数据 + */ + public String getDeviceResponse() { + return udp_device_res; + } + + /** + * 获取A门关闭时间 + */ + public int getGateACloseTime() { + return udp_gate_a_close_time; + } + + /** + * 停止UDP服务 + */ + public void stop() { + Log.i(TAG, "停止UDP服务"); + + stopPolling(); + isInitialized = false; + + notifyDeviceConnectionChange(false); + } + + /** + * 释放资源 + */ + public void release() { + Log.i(TAG, "释放UDP资源"); + + stop(); + + if (executorService != null && !executorService.isShutdown()) { + executorService.shutdown(); + } + + if (scheduledExecutor != null && !scheduledExecutor.isShutdown()) { + scheduledExecutor.shutdown(); + } + + stateListener = null; + deviceListener = null; + } + + // 通知方法 + private void notifyGateStateUpdate(boolean gateAState, boolean gateBState, String rawData) { + if (stateListener != null) { + mainHandler.post(() -> stateListener.onGateStateUpdate(gateAState, gateBState, rawData)); + } + } + + private void notifyGateOpenResult(String gateType, boolean success) { + if (stateListener != null) { + mainHandler.post(() -> stateListener.onGateOpenResult(gateType, success)); + } + } + + private void notifyUDPError(String error) { + if (stateListener != null) { + mainHandler.post(() -> stateListener.onUDPError(error)); + } + } + + private void notifyDeviceInfoUpdate(String deviceInfo) { + if (deviceListener != null) { + mainHandler.post(() -> deviceListener.onDeviceInfoUpdate(deviceInfo)); + } + } + + private void notifyDeviceConnectionChange(boolean connected) { + if (deviceListener != null) { + mainHandler.post(() -> deviceListener.onDeviceConnectionChange(connected)); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ouxuan/oxface/device/OxUDPUsageExample.java b/app/src/main/java/com/ouxuan/oxface/device/OxUDPUsageExample.java new file mode 100644 index 0000000..24fc007 --- /dev/null +++ b/app/src/main/java/com/ouxuan/oxface/device/OxUDPUsageExample.java @@ -0,0 +1,296 @@ +package com.ouxuan.oxface.device; + +import android.content.Context; +import android.util.Log; + +/** + * OxUDP门禁控制使用示例 + * 展示如何在人脸识别界面中集成门禁UDP控制功能 + * + * @author AI Assistant + * @version 1.0 + */ +public class OxUDPUsageExample { + + private static final String TAG = "OxUDPUsageExample"; + private OxUDP oxUDP; + private Context context; + + public OxUDPUsageExample(Context context) { + this.context = context; + this.oxUDP = OxUDP.getInstance(); + } + + /** + * 在人脸识别界面初始化UDP门禁控制 + */ + public void initializeInFaceActivity() { + // 设置状态监听器 + oxUDP.setStateListener(new OxUDP.UDPStateListener() { + @Override + public void onGateStateUpdate(boolean gateAState, boolean gateBState, String rawData) { + Log.i(TAG, "门禁状态更新 - A门: " + (gateAState ? "开启" : "关闭") + + ", B门: " + (gateBState ? "开启" : "关闭")); + + // 在人脸识别界面更新门禁状态显示 + updateGateStatusUI(gateAState, gateBState); + + // 根据门禁状态执行相应逻辑 + handleGateStateChange(gateAState, gateBState); + } + + @Override + public void onGateOpenResult(String gateType, boolean success) { + Log.i(TAG, gateType + "门开启结果: " + (success ? "成功" : "失败")); + + if (success) { + // 门开启成功,显示通过提示 + showGateOpenSuccessMessage(gateType); + } else { + // 门开启失败,显示错误信息 + showGateOpenFailedMessage(gateType); + } + } + + @Override + public void onUDPError(String error) { + Log.e(TAG, "UDP通信错误: " + error); + // 显示错误信息给用户 + showErrorMessage("门禁通信异常: " + error); + } + }); + + // 设置设备监听器 + oxUDP.setDeviceListener(new OxUDP.UDPDeviceListener() { + @Override + public void onDeviceInfoUpdate(String deviceInfo) { + Log.i(TAG, "设备信息更新: " + deviceInfo); + // 可以保存设备信息用于后续使用 + } + + @Override + public void onDeviceConnectionChange(boolean connected) { + Log.i(TAG, "设备连接状态: " + (connected ? "已连接" : "已断开")); + + if (connected) { + // 设备连接成功,开始轮询 + startGatePolling(); + } else { + // 设备连接失败,显示错误信息 + showErrorMessage("门禁设备连接失败"); + } + } + }); + + // 初始化UDP连接 + oxUDP.initUDP(context); + } + + /** + * 开始门禁状态轮询 + */ + public void startGatePolling() { + Log.i(TAG, "开始门禁状态轮询"); + oxUDP.startPolling(); + } + + /** + * 停止门禁状态轮询 + */ + public void stopGatePolling() { + Log.i(TAG, "停止门禁状态轮询"); + oxUDP.stopPolling(); + } + + /** + * 人脸识别成功后开门处理 + * @param isEntry true: 进门(开A门), false: 出门(开B门) + */ + public void handleFaceRecognitionSuccess(boolean isEntry) { + Log.i(TAG, "人脸识别成功,准备开门: " + (isEntry ? "进门(A门)" : "出门(B门)")); + + if (isEntry) { + // 进门 - 开启A门 + oxUDP.openGateA(); + } else { + // 出门 - 开启B门 + oxUDP.openGateB(); + } + } + + /** + * 更新界面上的门禁状态显示 + * @param gateAState A门状态 + * @param gateBState B门状态 + */ + private void updateGateStatusUI(boolean gateAState, boolean gateBState) { + // 在人脸识别界面更新门禁状态显示 + // 这里可以更新状态指示灯、文字显示等 + Log.d(TAG, "更新UI - A门状态: " + (gateAState ? "开启" : "关闭") + + ", B门状态: " + (gateBState ? "开启" : "关闭")); + + // 示例:更新状态文本 + // tvGateAStatus.setText("A门: " + (gateAState ? "开启" : "关闭")); + // tvGateBStatus.setText("B门: " + (gateBState ? "开启" : "关闭")); + + // 示例:更新状态指示器颜色 + // ivGateAIndicator.setImageResource(gateAState ? R.drawable.gate_open : R.drawable.gate_close); + // ivGateBIndicator.setImageResource(gateBState ? R.drawable.gate_open : R.drawable.gate_close); + } + + /** + * 处理门禁状态变化 + * @param gateAState A门状态 + * @param gateBState B门状态 + */ + private void handleGateStateChange(boolean gateAState, boolean gateBState) { + // 根据门禁状态执行相应的业务逻辑 + + if (gateAState && gateBState) { + // 两个门都开启,可能需要特殊处理 + Log.w(TAG, "警告:A门和B门同时开启"); + } + + // 可以根据门的状态来调整人脸识别的流程 + // 例如:只有在门关闭时才允许进行人脸识别 + } + + /** + * 显示门开启成功消息 + * @param gateType 门类型 + */ + private void showGateOpenSuccessMessage(String gateType) { + String message = gateType + "门已开启,请通过"; + Log.i(TAG, message); + + // 在界面上显示成功信息 + // Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); + // 或者显示在界面的状态栏中 + // tvStatusMessage.setText(message); + // tvStatusMessage.setTextColor(Color.GREEN); + } + + /** + * 显示门开启失败消息 + * @param gateType 门类型 + */ + private void showGateOpenFailedMessage(String gateType) { + String message = gateType + "门开启失败,请重试"; + Log.e(TAG, message); + + // 在界面上显示失败信息 + // Toast.makeText(context, message, Toast.LENGTH_LONG).show(); + // tvStatusMessage.setText(message); + // tvStatusMessage.setTextColor(Color.RED); + } + + /** + * 显示错误消息 + * @param error 错误信息 + */ + private void showErrorMessage(String error) { + Log.e(TAG, "显示错误消息: " + error); + + // 在界面上显示错误信息 + // Toast.makeText(context, error, Toast.LENGTH_LONG).show(); + // 或者在状态栏显示 + // tvStatusMessage.setText(error); + // tvStatusMessage.setTextColor(Color.RED); + } + + /** + * 设置A门关闭时间 + * @param seconds 关闭时间(秒) + */ + public void setGateACloseTime(int seconds) { + Log.i(TAG, "设置A门关闭时间: " + seconds + "秒"); + oxUDP.setGateACloseTime(seconds); + } + + /** + * 获取当前门禁状态信息 + */ + public void getGateStatusInfo() { + boolean isInitialized = oxUDP.isInitialized(); + boolean isPolling = oxUDP.isPolling(); + String deviceResponse = oxUDP.getDeviceResponse(); + int closeTime = oxUDP.getGateACloseTime(); + + Log.i(TAG, "门禁状态信息:"); + Log.i(TAG, "- 初始化状态: " + (isInitialized ? "已初始化" : "未初始化")); + Log.i(TAG, "- 轮询状态: " + (isPolling ? "进行中" : "已停止")); + Log.i(TAG, "- 设备响应: " + deviceResponse); + Log.i(TAG, "- A门关闭时间: " + closeTime + "秒"); + } + + /** + * 清理资源(在Activity销毁时调用) + */ + public void cleanup() { + Log.i(TAG, "清理UDP门禁控制资源"); + oxUDP.stop(); + } + + /** + * 释放所有资源(在应用退出时调用) + */ + public void release() { + Log.i(TAG, "释放UDP门禁控制所有资源"); + oxUDP.release(); + } + + /** + * 在人脸识别Activity中的完整集成示例 + */ + public static class FaceActivityIntegrationExample { + + private OxUDPUsageExample udpExample; + + /** + * 在onCreate中初始化 + */ + public void onCreateInitialize(Context context) { + udpExample = new OxUDPUsageExample(context); + udpExample.initializeInFaceActivity(); + + // 设置A门关闭时间为3秒 + udpExample.setGateACloseTime(3); + } + + /** + * 在onResume中开始轮询 + */ + public void onResumeStartPolling() { + if (udpExample != null) { + udpExample.startGatePolling(); + } + } + + /** + * 在onPause中停止轮询 + */ + public void onPauseStopPolling() { + if (udpExample != null) { + udpExample.stopGatePolling(); + } + } + + /** + * 在onDestroy中清理资源 + */ + public void onDestroyCleanup() { + if (udpExample != null) { + udpExample.cleanup(); + } + } + + /** + * 人脸识别成功回调处理 + */ + public void onFaceRecognitionSuccess(boolean isEntryGate) { + if (udpExample != null) { + udpExample.handleFaceRecognitionSuccess(isEntryGate); + } + } + } +} \ No newline at end of file