diff --git a/app/src/main/java/com/ouxuan/oxface/DebugActivity.java b/app/src/main/java/com/ouxuan/oxface/DebugActivity.java index 41d99f4..d932041 100644 --- a/app/src/main/java/com/ouxuan/oxface/DebugActivity.java +++ b/app/src/main/java/com/ouxuan/oxface/DebugActivity.java @@ -23,6 +23,7 @@ import com.baidu.idl.face.main.finance.listener.SdkInitListener; import com.baidu.idl.face.main.finance.manager.FaceSDKManager; import com.ouxuan.oxface.device.DeviceUtils; import com.ouxuan.oxface.device.HuaWeiScanManager; +import com.ouxuan.oxface.device.OxGpio; import com.ouxuan.oxface.device.RelayController; import com.ouxuan.oxface.network.utils.NetworkUtils; import com.ouxuan.oxface.utils.AutoStartManager; @@ -41,6 +42,7 @@ public class DebugActivity extends Activity { private ScrollView logScrollView; private AutoStartManager autoStartManager; private RelayController relayController; + private OxGpio oxGpio; @Override protected void onCreate(Bundle savedInstanceState) { @@ -63,6 +65,8 @@ public class DebugActivity extends Activity { private void initManagers() { autoStartManager = AutoStartManager.getInstance(this); relayController = RelayController.getInstance(); + oxGpio = OxGpio.getInstance(); + oxGpio.initialize(this); } private void setupClickListeners() { @@ -238,6 +242,15 @@ public class DebugActivity extends Activity { testRelayAutoClose(); } }); + + // 485人数测试按钮 + Button btnTest485PeopleNum = findViewById(R.id.btnTest485PeopleNum); + btnTest485PeopleNum.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + test485PeopleNum(); + } + }); } /** @@ -700,4 +713,48 @@ public class DebugActivity extends Activity { logMessage("未处理扫码结果,继续处理其他ActivityResult"); } } + + /** + * 测试485人数获取功能 + */ + private void test485PeopleNum() { + logMessage("开始485人数测试..."); + + if (oxGpio == null) { + logMessage("OxGpio未初始化"); + showToast("OxGpio未初始化"); + return; + } + + try { + // 设置485模式开关为启用状态 + oxGpio.setGateCamera485OxOn(true); + logMessage("已启用485模式"); + + // 调用sendHex485ForPeopleNum方法 + oxGpio.sendHex485ForPeopleNum(new OxGpio.PeopleNumCallback() { + @Override + public void onSuccess(int peopleNum) { + logMessage("485人数获取成功: " + peopleNum + " 人"); + Log.i(TAG, "485人数获取成功: " + peopleNum + " 人"); + showToast("485人数获取成功: " + peopleNum + " 人"); + } + + @Override + public void onError(String errorMessage) { + logMessage("485人数获取失败: " + errorMessage); + Log.e(TAG, "485人数获取失败: " + errorMessage); + showToast("485人数获取失败: " + errorMessage); + } + }); + + logMessage("已发送485人数查询请求,等待响应..."); + showToast("正在查询485人数..."); + + } catch (Exception e) { + Log.e(TAG, "485人数测试失败", e); + logMessage("485人数测试失败: " + e.getMessage()); + showToast("485人数测试失败"); + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/ouxuan/oxface/device/OxGpio.java b/app/src/main/java/com/ouxuan/oxface/device/OxGpio.java new file mode 100644 index 0000000..dc055e9 --- /dev/null +++ b/app/src/main/java/com/ouxuan/oxface/device/OxGpio.java @@ -0,0 +1,511 @@ +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(); + } + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_debug.xml b/app/src/main/res/layout/activity_debug.xml index 21cc26f..bf7970a 100644 --- a/app/src/main/res/layout/activity_debug.xml +++ b/app/src/main/res/layout/activity_debug.xml @@ -229,14 +229,15 @@ android:layout_marginEnd="4dp" android:textSize="12sp" /> -