You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
511 lines
16 KiB
511 lines
16 KiB
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<Integer> 人数结果
|
|
*/
|
|
public CompletableFuture<Integer> sendHex485ForPeopleNumAsync() {
|
|
LogManager.logInfo(TAG, "异步获取摄像头人数");
|
|
|
|
CompletableFuture<Integer> 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();
|
|
}
|
|
}
|
|
}
|