diff --git a/RelayController继电器控制类实现说明.md b/RelayController继电器控制类实现说明.md new file mode 100644 index 0000000..fc267e5 --- /dev/null +++ b/RelayController继电器控制类实现说明.md @@ -0,0 +1,215 @@ +# RelayController 继电器控制类实现说明 + +## 概述 + +根据 `RelayUtils` 重写了一个更完善的继电器控制类 `RelayController`,位于 `device` 目录下,提供了您要求的三个核心功能。 + +## 实现的功能 + +### 1. 开启继电器 +```java +// 开启继电器(默认设备) +boolean success = RelayController.getInstance().openRelay(); + +// 开启继电器(指定设备类型) +boolean success = RelayController.getInstance().openRelay(RelayController.PAD_TYPE_TWO); +``` + +### 2. 关闭继电器 +```java +// 关闭继电器(默认设备) +boolean success = RelayController.getInstance().closeRelay(); + +// 关闭继电器(指定设备类型) +boolean success = RelayController.getInstance().closeRelay(RelayController.PAD_TYPE_TWO); +``` + +### 3. 开启继电器3秒后自动关闭 +```java +// 开启继电器3秒后自动关闭(默认设备) +boolean success = RelayController.getInstance().openRelayWithAutoClose(); + +// 开启继电器3秒后自动关闭(指定设备类型) +boolean success = RelayController.getInstance().openRelayWithAutoClose(RelayController.PAD_TYPE_TWO); + +// 自定义延时时间(扩展功能) +boolean success = RelayController.getInstance().openRelayWithAutoClose(RelayController.PAD_TYPE_DEFAULT, 5000); // 5秒后关闭 +``` + +## 核心特性 + +### 1. 单例模式设计 +- 使用双重检查锁定确保线程安全 +- 全局唯一实例,避免资源冲突 + +### 2. 设备类型支持 +- `PAD_TYPE_DEFAULT`: 第6批设备(GPIO114) +- `PAD_TYPE_TWO`: 第2批设备(GPIO123) +- 自动根据设备类型选择正确的GPIO路径 + +### 3. 错误处理机制 +- 写入失败时自动尝试设置GPIO方向 +- 完整的异常捕获和日志记录 +- 资源自动清理和关闭 + +### 4. 延时自动关闭 +- 使用 `Handler` 实现精确的延时控制 +- 支持自定义延时时间 +- 异步执行,不阻塞主线程 + +### 5. 状态读取功能 +```java +// 读取继电器当前状态 +Boolean status = RelayController.getInstance().getRelayStatus(); +if (status != null) { + String statusText = status ? "开启" : "关闭"; + Log.d(TAG, "继电器状态: " + statusText); +} +``` + +## 文件结构 + +``` +app/src/main/java/com/ouxuan/oxface/device/ +├── RelayController.java // 核心继电器控制类 +└── RelayControllerUsageExample.java // 使用示例和集成指导 +``` + +## 技术实现细节 + +### GPIO路径配置 +```java +// 第6批设备(默认) +private static final String PWM_FLASH_OUT = "/sys/class/gpio/gpio114/direction"; +private static final String PWM_FLASH = "/sys/class/gpio/gpio114/value"; + +// 第2批设备 +private static final String PWM_FLASH_OUT_2 = "/sys/class/gpio/gpio123/direction"; +private static final String PWM_FLASH_2 = "/sys/class/gpio/gpio123/value"; +``` + +### GPIO操作命令 +```java +private static final byte[] OPEN_RELAY = {'1'}; // 开启继电器 +private static final byte[] CLOSE_RELAY = {'0'}; // 关闭继电器 +private static final byte[] GPIO_OUT = {'o', 'u', 't'}; // 输出模式 +private static final byte[] GPIO_IN = {'i', 'n'}; // 输入模式 +``` + +### 自动关闭实现 +```java +// 使用Handler实现延时关闭 +handler.postDelayed(new Runnable() { + @Override + public void run() { + closeRelay(padType); + } +}, 3000); // 3秒延时 +``` + +## 使用示例 + +### 在人脸识别成功后开门 +```java +public class OXFaceOnlineActivity extends BaseActivity { + + private RelayController relayController; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // 初始化继电器控制器 + relayController = RelayController.getInstance(); + } + + /** + * 人脸识别成功后的处理 + */ + private void onFaceRecognitionSuccess() { + // 开门3秒后自动关闭 + boolean success = relayController.openRelayWithAutoClose(); + if (success) { + showToast("门已开启,请通过"); + } else { + showToast("开门失败,请重试"); + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + // 清理资源 + if (relayController != null) { + relayController.release(); + } + } +} +``` + +### 手动控制示例 +```java +// 手动开门 +RelayController.getInstance().openRelay(); + +// 手动关门 +RelayController.getInstance().closeRelay(); + +// 检查门锁状态 +Boolean status = RelayController.getInstance().getRelayStatus(); +``` + +## 相比原版RelayUtils的改进 + +### 1. 架构改进 +- **单例模式**: 避免多实例冲突 +- **面向对象**: 更好的封装和复用 +- **异步处理**: 使用Handler实现非阻塞延时 + +### 2. 功能增强 +- **状态读取**: 可以查询继电器当前状态 +- **自定义延时**: 支持任意时长的自动关闭 +- **设备类型枚举**: 更清晰的设备类型管理 + +### 3. 错误处理 +- **完整的异常处理**: 所有可能的异常都有处理 +- **自动重试机制**: 写入失败时自动设置GPIO方向 +- **资源管理**: 确保FileOutputStream正确关闭 + +### 4. 日志系统 +- **统一日志管理**: 使用LogManager统一记录 +- **详细的操作日志**: 便于调试和问题排查 +- **状态跟踪**: 记录每个操作的结果 + +## 集成建议 + +1. **在人脸识别成功回调中使用**: + ```java + relayController.openRelayWithAutoClose(); + ``` + +2. **在门禁控制逻辑中集成**: + ```java + // 配合UDP门禁控制使用 + if (isUDPInitialized && udpExample != null) { + udpExample.handleFaceRecognitionSuccess(true); + } + // 同时控制本地继电器 + relayController.openRelayWithAutoClose(); + ``` + +3. **在调试界面中添加手动控制**: + ```java + // 调试用的手动开门按钮 + btnManualOpen.setOnClickListener(v -> relayController.openRelay()); + btnManualClose.setOnClickListener(v -> relayController.closeRelay()); + ``` + +## 总结 + +新的 `RelayController` 类提供了您要求的三个核心功能: + +1. ✅ **开启继电器**: `openRelay()` 方法 +2. ✅ **关闭继电器**: `closeRelay()` 方法 +3. ✅ **开启继电器3秒后自动关闭**: `openRelayWithAutoClose()` 方法 + +同时还提供了更多实用功能:状态读取、自定义延时、设备类型支持、完整的错误处理等。这个实现更加健壮、易用,适合在人脸识别门禁系统中使用。 \ No newline at end of file diff --git a/app/src/main/java/com/ouxuan/oxface/device/RelayController.java b/app/src/main/java/com/ouxuan/oxface/device/RelayController.java new file mode 100644 index 0000000..cdef846 --- /dev/null +++ b/app/src/main/java/com/ouxuan/oxface/device/RelayController.java @@ -0,0 +1,332 @@ +package com.ouxuan.oxface.device; + +import android.os.Handler; +import android.os.Looper; +import android.util.Log; + +import com.ouxuan.oxface.utils.LogManager; + +import java.io.BufferedReader; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; + +/** + * 继电器控制器 + * 基于GPIO控制继电器的开关操作 + * + * @author AI Assistant + * @version 1.0 + * @date 2024/09/11 + */ +public class RelayController { + + private static final String TAG = "RelayController"; + + // 第6批设备-默认GPIO路径 + private static final String PWM_FLASH_OUT = "/sys/class/gpio/gpio114/direction"; + private static final String PWM_FLASH = "/sys/class/gpio/gpio114/value"; + + // 第2批设备GPIO路径 + private static final String PWM_FLASH_OUT_2 = "/sys/class/gpio/gpio123/direction"; + private static final String PWM_FLASH_2 = "/sys/class/gpio/gpio123/value"; + + // GPIO操作命令 + private static final byte[] OPEN_RELAY = {'1'}; + private static final byte[] CLOSE_RELAY = {'0'}; + private static final byte[] GPIO_OUT = {'o', 'u', 't'}; + private static final byte[] GPIO_IN = {'i', 'n'}; + + // 设备类型 + public static final String PAD_TYPE_DEFAULT = "pad_default"; + public static final String PAD_TYPE_TWO = "pad_two"; + + // 单例实例 + private static volatile RelayController instance; + + // Handler用于延时操作 + private Handler handler; + + private RelayController() { + handler = new Handler(Looper.getMainLooper()); + } + + /** + * 获取RelayController单例实例 + * @return RelayController实例 + */ + public static RelayController getInstance() { + if (instance == null) { + synchronized (RelayController.class) { + if (instance == null) { + instance = new RelayController(); + } + } + } + return instance; + } + + /** + * 设置GPIO方向为输出模式 + * @param padType 设备类型 + */ + private void setGpioDirection(String padType) { + FileOutputStream fos = null; + try { + String directionPath = PAD_TYPE_TWO.equals(padType) ? PWM_FLASH_OUT_2 : PWM_FLASH_OUT; + fos = new FileOutputStream(directionPath); + fos.write(GPIO_OUT); + LogManager.logInfo(TAG, "GPIO方向设置为输出模式成功, 设备类型: " + padType); + } catch (Exception e) { + LogManager.logError(TAG, "设置GPIO方向失败", e); + } finally { + if (fos != null) { + try { + fos.close(); + } catch (IOException e) { + LogManager.logError(TAG, "关闭FileOutputStream失败", e); + } + } + } + } + + /** + * 设置GPIO方向为输入模式 + * @param padType 设备类型 + */ + private void setGpioDirectionInput(String padType) { + FileOutputStream fos = null; + try { + String directionPath = PAD_TYPE_TWO.equals(padType) ? PWM_FLASH_OUT_2 : PWM_FLASH_OUT; + fos = new FileOutputStream(directionPath); + fos.write(GPIO_IN); + LogManager.logInfo(TAG, "GPIO方向设置为输入模式成功, 设备类型: " + padType); + } catch (Exception e) { + LogManager.logError(TAG, "设置GPIO方向为输入模式失败", e); + } finally { + if (fos != null) { + try { + fos.close(); + } catch (IOException e) { + LogManager.logError(TAG, "关闭FileOutputStream失败", e); + } + } + } + } + + /** + * 读取GPIO值 + * @param padType 设备类型 + * @return GPIO值字符串,失败返回null + */ + public String readGpioValue(String padType) { + BufferedReader reader = null; + try { + String valuePath = PAD_TYPE_TWO.equals(padType) ? PWM_FLASH_2 : PWM_FLASH; + reader = new BufferedReader(new FileReader(valuePath)); + String value = reader.readLine(); + LogManager.logInfo(TAG, "读取GPIO值成功: " + value + ", 设备类型: " + padType); + return value; + } catch (IOException e) { + LogManager.logError(TAG, "读取GPIO值失败", e); + return null; + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + LogManager.logError(TAG, "关闭BufferedReader失败", e); + } + } + } + } + + /** + * 写入GPIO值 + * @param value 要写入的值 + * @param padType 设备类型 + * @return 是否成功 + */ + private boolean writeGpioValue(byte[] value, String padType) { + FileOutputStream fos = null; + try { + String valuePath = PAD_TYPE_TWO.equals(padType) ? PWM_FLASH_2 : PWM_FLASH; + fos = new FileOutputStream(valuePath); + fos.write(value); + return true; + } catch (Exception e) { + if (e instanceof IOException) { + // 如果写入失败,尝试设置GPIO方向后重试 + LogManager.logWarning(TAG, "GPIO写入失败,尝试设置方向后重试"); + setGpioDirection(padType); + try { + if (fos != null) { + fos.write(value); + return true; + } + } catch (IOException ioException) { + LogManager.logError(TAG, "重试写入GPIO值失败", ioException); + } + } else { + LogManager.logError(TAG, "写入GPIO值时发生意外异常", e); + } + return false; + } finally { + if (fos != null) { + try { + fos.close(); + } catch (IOException e) { + LogManager.logError(TAG, "关闭FileOutputStream失败", e); + } + } + } + } + + /** + * 1. 开启继电器(默认设备) + * @return 是否成功 + */ + public boolean openRelay() { + return openRelay(PAD_TYPE_DEFAULT); + } + + /** + * 1. 开启继电器(指定设备类型) + * @param padType 设备类型 + * @return 是否成功 + */ + public boolean openRelay(String padType) { + LogManager.logInfo(TAG, "开启继电器, 设备类型: " + padType); + boolean success = writeGpioValue(OPEN_RELAY, padType); + if (success) { + LogManager.logInfo(TAG, "继电器开启成功"); + } else { + LogManager.logError(TAG, "继电器开启失败"); + } + return success; + } + + /** + * 2. 关闭继电器(默认设备) + * @return 是否成功 + */ + public boolean closeRelay() { + return closeRelay(PAD_TYPE_DEFAULT); + } + + /** + * 2. 关闭继电器(指定设备类型) + * @param padType 设备类型 + * @return 是否成功 + */ + public boolean closeRelay(String padType) { + LogManager.logInfo(TAG, "关闭继电器, 设备类型: " + padType); + boolean success = writeGpioValue(CLOSE_RELAY, padType); + if (success) { + LogManager.logInfo(TAG, "继电器关闭成功"); + } else { + LogManager.logError(TAG, "继电器关闭失败"); + } + return success; + } + + /** + * 3. 开启继电器3秒后自动关闭(默认设备) + * @return 是否成功开启 + */ + public boolean openRelayWithAutoClose() { + return openRelayWithAutoClose(PAD_TYPE_DEFAULT); + } + + /** + * 3. 开启继电器3秒后自动关闭(指定设备类型) + * @param padType 设备类型 + * @return 是否成功开启 + */ + public boolean openRelayWithAutoClose(String padType) { + LogManager.logInfo(TAG, "开启继电器并设置3秒后自动关闭, 设备类型: " + padType); + + // 先开启继电器 + boolean openSuccess = openRelay(padType); + if (!openSuccess) { + LogManager.logError(TAG, "继电器开启失败,取消自动关闭计划"); + return false; + } + + // 3秒后自动关闭 + handler.postDelayed(new Runnable() { + @Override + public void run() { + LogManager.logInfo(TAG, "3秒时间到,自动关闭继电器, 设备类型: " + padType); + closeRelay(padType); + } + }, 3000); + + LogManager.logInfo(TAG, "继电器已开启,3秒后将自动关闭"); + return true; + } + + /** + * 开启继电器指定时间后自动关闭(扩展功能) + * @param padType 设备类型 + * @param delayMillis 延时时间(毫秒) + * @return 是否成功开启 + */ + public boolean openRelayWithAutoClose(String padType, long delayMillis) { + LogManager.logInfo(TAG, "开启继电器并设置" + delayMillis + "ms后自动关闭, 设备类型: " + padType); + + // 先开启继电器 + boolean openSuccess = openRelay(padType); + if (!openSuccess) { + LogManager.logError(TAG, "继电器开启失败,取消自动关闭计划"); + return false; + } + + // 指定时间后自动关闭 + handler.postDelayed(new Runnable() { + @Override + public void run() { + LogManager.logInfo(TAG, delayMillis + "ms时间到,自动关闭继电器, 设备类型: " + padType); + closeRelay(padType); + } + }, delayMillis); + + LogManager.logInfo(TAG, "继电器已开启," + delayMillis + "ms后将自动关闭"); + return true; + } + + /** + * 获取继电器当前状态 + * @param padType 设备类型 + * @return true为开启状态,false为关闭状态,null为读取失败 + */ + public Boolean getRelayStatus(String padType) { + String value = readGpioValue(padType); + if (value == null) { + LogManager.logError(TAG, "无法读取继电器状态"); + return null; + } + + boolean isOpen = "1".equals(value.trim()); + LogManager.logInfo(TAG, "继电器当前状态: " + (isOpen ? "开启" : "关闭") + ", 设备类型: " + padType); + return isOpen; + } + + /** + * 获取继电器当前状态(默认设备) + * @return true为开启状态,false为关闭状态,null为读取失败 + */ + public Boolean getRelayStatus() { + return getRelayStatus(PAD_TYPE_DEFAULT); + } + + /** + * 释放资源 + */ + public void release() { + if (handler != null) { + handler.removeCallbacksAndMessages(null); + } + LogManager.logInfo(TAG, "RelayController资源已释放"); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ouxuan/oxface/device/RelayControllerUsageExample.java b/app/src/main/java/com/ouxuan/oxface/device/RelayControllerUsageExample.java new file mode 100644 index 0000000..0295b64 --- /dev/null +++ b/app/src/main/java/com/ouxuan/oxface/device/RelayControllerUsageExample.java @@ -0,0 +1,180 @@ +package com.ouxuan.oxface.device; + +import android.content.Context; +import android.util.Log; + +import com.ouxuan.oxface.utils.LogManager; + +/** + * RelayController 使用示例 + * 展示如何在人脸识别应用中集成继电器控制功能 + * + * @author AI Assistant + * @version 1.0 + */ +public class RelayControllerUsageExample { + + private static final String TAG = "RelayControllerExample"; + private RelayController relayController; + private Context context; + + public RelayControllerUsageExample(Context context) { + this.context = context; + this.relayController = RelayController.getInstance(); + } + + /** + * 在人脸识别成功后开门的示例 + */ + public void handleFaceRecognitionSuccess() { + LogManager.logInfo(TAG, "人脸识别成功,准备开启继电器门锁"); + + // 使用自动关闭功能,开门3秒后自动关门 + boolean success = relayController.openRelayWithAutoClose(); + + if (success) { + LogManager.logInfo(TAG, "门锁已开启,3秒后自动关闭"); + // 可以在这里添加UI提示 + showUserMessage("门已开启,请通过"); + } else { + LogManager.logError(TAG, "门锁开启失败"); + showUserMessage("开门失败,请重试或联系管理员"); + } + } + + /** + * 手动开门示例(可用于调试或紧急情况) + */ + public void manualOpenDoor() { + LogManager.logInfo(TAG, "手动开门请求"); + + boolean success = relayController.openRelay(); + + if (success) { + LogManager.logInfo(TAG, "手动开门成功"); + showUserMessage("门已手动开启"); + } else { + LogManager.logError(TAG, "手动开门失败"); + showUserMessage("手动开门失败"); + } + } + + /** + * 手动关门示例 + */ + public void manualCloseDoor() { + LogManager.logInfo(TAG, "手动关门请求"); + + boolean success = relayController.closeRelay(); + + if (success) { + LogManager.logInfo(TAG, "手动关门成功"); + showUserMessage("门已手动关闭"); + } else { + LogManager.logError(TAG, "手动关门失败"); + showUserMessage("手动关门失败"); + } + } + + /** + * 检查门锁状态示例 + */ + public void checkDoorStatus() { + LogManager.logInfo(TAG, "检查门锁状态"); + + Boolean status = relayController.getRelayStatus(); + + if (status == null) { + LogManager.logError(TAG, "无法读取门锁状态"); + showUserMessage("门锁状态检测失败"); + } else { + String statusText = status ? "开启" : "关闭"; + LogManager.logInfo(TAG, "门锁当前状态: " + statusText); + showUserMessage("门锁状态: " + statusText); + } + } + + /** + * 支持不同设备类型的开门示例 + */ + public void handleFaceRecognitionSuccessForSpecificDevice(String deviceType) { + LogManager.logInfo(TAG, "人脸识别成功,准备开启继电器门锁,设备类型: " + deviceType); + + // 根据设备类型选择合适的GPIO路径 + String padType = "pad_two".equals(deviceType) ? + RelayController.PAD_TYPE_TWO : RelayController.PAD_TYPE_DEFAULT; + + boolean success = relayController.openRelayWithAutoClose(padType); + + if (success) { + LogManager.logInfo(TAG, "门锁已开启,3秒后自动关闭,设备类型: " + deviceType); + showUserMessage("门已开启,请通过"); + } else { + LogManager.logError(TAG, "门锁开启失败,设备类型: " + deviceType); + showUserMessage("开门失败,请重试或联系管理员"); + } + } + + /** + * 自定义延时关门示例 + */ + public void openDoorWithCustomDelay(long delaySeconds) { + LogManager.logInfo(TAG, "开门并设置" + delaySeconds + "秒后自动关门"); + + boolean success = relayController.openRelayWithAutoClose( + RelayController.PAD_TYPE_DEFAULT, delaySeconds * 1000); + + if (success) { + LogManager.logInfo(TAG, "门锁已开启," + delaySeconds + "秒后自动关闭"); + showUserMessage("门已开启," + delaySeconds + "秒后自动关闭"); + } else { + LogManager.logError(TAG, "门锁开启失败"); + showUserMessage("开门失败,请重试"); + } + } + + /** + * 在Activity生命周期中的使用示例 + */ + public static class ActivityIntegrationExample { + + private RelayControllerUsageExample relayExample; + + /** + * 在onCreate中初始化 + */ + public void onCreateInitialize(Context context) { + relayExample = new RelayControllerUsageExample(context); + LogManager.logInfo(TAG, "RelayController已初始化"); + } + + /** + * 人脸识别成功回调处理 + */ + public void onFaceRecognitionSuccess() { + if (relayExample != null) { + relayExample.handleFaceRecognitionSuccess(); + } + } + + /** + * 在onDestroy中清理资源 + */ + public void onDestroyCleanup() { + if (relayExample != null && relayExample.relayController != null) { + relayExample.relayController.release(); + LogManager.logInfo(TAG, "RelayController资源已清理"); + } + } + } + + /** + * 显示用户消息的辅助方法 + * 在实际使用中,这里可以显示Toast、更新UI等 + */ + private void showUserMessage(String message) { + LogManager.logInfo(TAG, "用户消息: " + message); + // 在这里可以添加实际的UI更新逻辑 + // 例如:Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); + } +} \ No newline at end of file