package com.ouxuan.oxface.device; import android.os.Handler; import android.os.Looper; import android.os.SystemClock; import com.ouxuan.oxface.utils.LogManager; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.DataOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; /** * OxGpio GPIO控制和485通信模块 * 基于原GpioUtils重写,并集成485串口通信功能 * * @author AI Assistant * @version 1.0 * @date 2024/09/11 */ public class OxGpio { private static final String TAG = "OxGpio"; // GPIO引脚定义 - RK3399 public static final int IO1_3399 = 1066; public static final int IO2_3399 = 1067; public static final int IO3_3399 = 1072; public static final int IO4_3399 = 1071; // GPIO引脚定义 - RK3368 public static final int IO1_3368 = 91; public static final int IO2_3368 = 90; public static final int IO3_3368 = 111; public static final int IO4_3368 = 109; // 485通信配置 private static final String SERIAL_PORT_PATH = "/dev/ttyS6"; private static final int BAUD_RATE = 9600; private static final int 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 OxGpio instance; // 485通信相关 private SerialPort485 serialPort485; private Handler mainHandler; private boolean gateCamera485OxOn = false; // GPIO读取失败计数 private static long mTime = 0; private static int mFailTimes = 0; /** * 485通信回调接口 */ public interface PeopleNumCallback { void onSuccess(int peopleNum); void onError(String errorMessage); } private OxGpio() { mainHandler = new Handler(Looper.getMainLooper()); // 初始化SerialPort485实例 serialPort485 = SerialPort485.getInstance(); } /** * 获取OxGpio单例实例 * @return OxGpio实例 */ public static OxGpio getInstance() { if (instance == null) { synchronized (OxGpio.class) { if (instance == null) { instance = new OxGpio(); } } } return instance; } /** * 初始化OxGpio模块 * @param context 上下文 */ public void initialize(android.content.Context context) { // 初始化SerialPort485 serialPort485.initialize(context); LogManager.logInfo(TAG, "OxGpio模块初始化完成"); } // ==================== GPIO操作方法 ==================== /** * 给export申请权限 */ public static boolean upgradeRootPermissionForExport() { LogManager.logInfo(TAG, "申请GPIO export权限"); return upgradeRootPermission("/sys/class/gpio/export"); } /** * 配置一个gpio路径 * @param gpio GPIO引脚号 * @return 是否成功 */ public static boolean exportGpio(int gpio) { LogManager.logInfo(TAG, "导出GPIO: " + gpio); return writeNode("/sys/class/gpio/export", String.valueOf(gpio)); } /** * 给获取io口的状态的路径申请权限,该方法需要在exportGpio后调用 * @param gpio GPIO引脚号 */ public static boolean upgradeRootPermissionForGpio(int gpio) { LogManager.logInfo(TAG, "申请GPIO权限: " + gpio); boolean directionResult = upgradeRootPermission("/sys/class/gpio/gpio" + gpio + "/direction"); boolean valueResult = upgradeRootPermission("/sys/class/gpio/gpio" + gpio + "/value"); return directionResult && valueResult; } /** * 设置io口为输入或输出 * @param gpio GPIO引脚号 * @param direction 0=输出(out), 1=输入(in) * @return 是否成功 */ public static boolean setGpioDirection(int gpio, int direction) { String gpioDirection; if (direction == 0) { gpioDirection = "out"; } else if (direction == 1) { gpioDirection = "in"; } else { LogManager.logError(TAG, "无效的GPIO方向: " + direction); return false; } LogManager.logInfo(TAG, "设置GPIO方向 - 引脚: " + gpio + ", 方向: " + gpioDirection); return writeNode("/sys/class/gpio/gpio" + gpio + "/direction", gpioDirection); } /** * 获取io口的状态为输出还是输入 * @param gpio GPIO引脚号 * @return "in" 或 "out" */ public static String getGpioDirection(int gpio) { String direction = readNode("/sys/class/gpio/gpio" + gpio + "/direction"); LogManager.logDebug(TAG, "获取GPIO方向 - 引脚: " + gpio + ", 方向: " + direction); return direction; } /** * 给输出io口写值,高电平或低电平 * @param gpio GPIO引脚号 * @param value "1"=高电平, "0"=低电平 * @return 是否成功 */ public static boolean writeGpioValue(int gpio, String value) { LogManager.logInfo(TAG, "写入GPIO值 - 引脚: " + gpio + ", 值: " + value); return writeNode("/sys/class/gpio/gpio" + gpio + "/value", value); } /** * 获取当前gpio是高还是低 * @param gpio GPIO引脚号 * @return "1"=高电平, "0"=低电平 */ public static String getGpioValue(int gpio) { String value = readNode("/sys/class/gpio/gpio" + gpio + "/value"); LogManager.logDebug(TAG, "获取GPIO值 - 引脚: " + gpio + ", 值: " + value); return value; } // ==================== 485通信方法(转换自uniapp) ==================== /** * 设置485模式开关 * @param enabled 是否启用485模式 */ public void setGateCamera485OxOn(boolean enabled) { this.gateCamera485OxOn = enabled; serialPort485.setGateCamera485OxOn(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; } // 使用SerialPort485进行通信 serialPort485.sendHex485ForPeopleNum(new SerialPort485.SerialPort485Callback() { @Override public void onSuccess(int peopleNum) { LogManager.logInfo(TAG, "485获取人数成功: " + peopleNum); if (callback != null) { mainHandler.post(() -> callback.onSuccess(peopleNum)); } } @Override public void onError(String errorMessage) { LogManager.logError(TAG, "485获取人数失败: " + errorMessage); if (callback != null) { mainHandler.post(() -> callback.onError(errorMessage)); } } }); } /** * 异步获取摄像头人数(使用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串口"); serialPort485.checkIsNeedOpen485(); } /** * 发送自定义485 HEX命令 * @param hexCommand HEX命令字符串 */ public void send485HexCommand(String hexCommand) { LogManager.logInfo(TAG, "发送485 HEX命令: " + hexCommand); serialPort485.sendHex(hexCommand); } /** * 获取485串口状态 * @return 串口状态描述 */ public String get485Status() { return serialPort485.getSerialPortStatus(); } /** * 关闭485串口 */ public void close485SerialPort() { LogManager.logInfo(TAG, "关闭485串口"); serialPort485.closeSerialPort(); } // ==================== 内部辅助方法 ==================== /** * 申请root权限 * @param path 路径 * @return 是否成功 */ private static boolean upgradeRootPermission(String path) { LogManager.logDebug(TAG, "申请root权限: " + path); Process process = null; DataOutputStream os = null; try { String cmd = "chmod 777 " + path; process = Runtime.getRuntime().exec("su"); // 切换到root账号 os = new DataOutputStream(process.getOutputStream()); os.writeBytes(cmd + "\n"); os.writeBytes("exit\n"); os.flush(); process.waitFor(); } catch (Exception e) { LogManager.logError(TAG, "申请root权限异常: " + path, e); return false; } finally { try { if (os != null) { os.close(); } if (process != null) { process.destroy(); } } catch (Exception e) { LogManager.logError(TAG, "关闭进程异常", e); } } try { boolean result = process != null && process.waitFor() == 0; LogManager.logInfo(TAG, "申请root权限结果: " + result + " - " + path); return result; } catch (InterruptedException e) { LogManager.logError(TAG, "等待进程中断", e); return false; } } /** * 写入节点 * @param path 节点路径 * @param value 写入值 * @return 是否成功 */ private static boolean writeNode(String path, String value) { LogManager.logDebug(TAG, "写入节点 - 路径: " + path + ", 值: " + value); if (path == null || value == null) { LogManager.logError(TAG, "写入节点参数错误 - 路径或值为空"); return false; } FileWriter fileWriter = null; try { fileWriter = new FileWriter(path); fileWriter.write(value); LogManager.logDebug(TAG, "节点写入成功: " + path); return true; } catch (Exception e) { LogManager.logError(TAG, "写入节点异常 - 路径: " + path + ", 值: " + value, e); return false; } finally { try { if (fileWriter != null) { fileWriter.close(); } } catch (IOException e) { LogManager.logError(TAG, "关闭FileWriter异常", e); } } } /** * 读取节点 * @param path 节点路径 * @return 读取的值 */ private static String readNode(String path) { LogManager.logDebug(TAG, "读取节点: " + path); String result = ""; FileReader fread = null; BufferedReader buffer = null; try { fread = new FileReader(path); buffer = new BufferedReader(fread); String str; while ((str = buffer.readLine()) != null) { result = str; break; } mFailTimes = 0; LogManager.logDebug(TAG, "节点读取成功: " + path + " = " + result); } catch (IOException e) { LogManager.logError(TAG, "读取节点IO异常: " + path, e); if (mTime == 0 || SystemClock.uptimeMillis() - mTime < 1000) { mFailTimes++; } if (mFailTimes >= 3) { LogManager.logWarning(TAG, "读取节点连续失败3次,可能需要检查权限: " + path); } } finally { try { if (buffer != null) { buffer.close(); } if (fread != null) { fread.close(); } } catch (IOException e) { LogManager.logError(TAG, "关闭文件流异常", e); } } return result; } // ==================== 便捷方法 ==================== /** * 初始化GPIO引脚(包含导出、申请权限、设置方向) * @param gpio GPIO引脚号 * @param direction 0=输出, 1=输入 * @return 是否成功 */ public static boolean initializeGpio(int gpio, int direction) { LogManager.logInfo(TAG, "初始化GPIO - 引脚: " + gpio + ", 方向: " + direction); // 1. 申请export权限 if (!upgradeRootPermissionForExport()) { LogManager.logError(TAG, "申请export权限失败"); return false; } // 2. 导出GPIO if (!exportGpio(gpio)) { LogManager.logError(TAG, "导出GPIO失败: " + gpio); return false; } // 3. 申请GPIO权限 if (!upgradeRootPermissionForGpio(gpio)) { LogManager.logError(TAG, "申请GPIO权限失败: " + gpio); return false; } // 4. 设置GPIO方向 if (!setGpioDirection(gpio, direction)) { LogManager.logError(TAG, "设置GPIO方向失败: " + gpio); return false; } LogManager.logInfo(TAG, "GPIO初始化成功: " + gpio); return true; } /** * 设置GPIO输出高电平 * @param gpio GPIO引脚号 * @return 是否成功 */ public static boolean setGpioHigh(int gpio) { LogManager.logInfo(TAG, "设置GPIO高电平: " + gpio); return writeGpioValue(gpio, "1"); } /** * 设置GPIO输出低电平 * @param gpio GPIO引脚号 * @return 是否成功 */ public static boolean setGpioLow(int gpio) { LogManager.logInfo(TAG, "设置GPIO低电平: " + gpio); return writeGpioValue(gpio, "0"); } /** * 检查GPIO是否为高电平 * @param gpio GPIO引脚号 * @return true表示高电平 */ public static boolean isGpioHigh(int gpio) { String value = getGpioValue(gpio); return "1".equals(value); } /** * 检查GPIO是否为低电平 * @param gpio GPIO引脚号 * @return true表示低电平 */ public static boolean isGpioLow(int gpio) { String value = getGpioValue(gpio); return "0".equals(value); } /** * 释放资源 */ public void release() { LogManager.logInfo(TAG, "释放OxGpio资源"); if (serialPort485 != null) { serialPort485.release(); } } }