diff --git a/app/src/main/java/com/ismart/ib86/app/MainActivity.java b/app/src/main/java/com/ismart/ib86/app/MainActivity.java index 1a710e9..2983973 100644 --- a/app/src/main/java/com/ismart/ib86/app/MainActivity.java +++ b/app/src/main/java/com/ismart/ib86/app/MainActivity.java @@ -30,6 +30,7 @@ import com.ismart.ib86.feature.serial.SerialPort.ASR5515DeviceManager; import com.ismart.ib86.feature.serial.SerialPort.ASR5515Protocol; import com.ismart.ib86.common.utils.LogManager; import com.ismart.ib86.feature.gpio.GpioTest; +import com.ismart.ib86.feature.motor.MotorController; import com.ismart.ib86.view.RobotEyesView; public class MainActivity extends AppCompatActivity { @@ -47,6 +48,7 @@ public class MainActivity extends AppCompatActivity { private static final int BT_POWER_EN = 75; private ASR5515DeviceManager deviceManager; + private MotorController motorController; // 使用弱引用避免内存泄漏 private Runnable wearDetectionRunnable = new Runnable() { @Override @@ -80,18 +82,10 @@ public class MainActivity extends AppCompatActivity { // 初始化UI控件 ivFace = (ImageView) this.findViewById(R.id.iv_face); - // btnPauseResume = findViewById(R.id.btn_pause_resume); - // btnSwitchFace = findViewById(R.id.btn_switch_face); - // seekBarSpeed = findViewById(R.id.seek_bar_speed); - // tvCurrentFace = findViewById(R.id.tv_current_face); - // tvSpeed = findViewById(R.id.tv_speed); // 初始化动画管理器 animationManager = new RobotFaceAnimationManager(this, ivFace); - // 启动随机表情动画 - playRandomFaceAnimation(); - // 每5秒自动切换一次表情 handler.postDelayed(new Runnable() { @Override @@ -100,59 +94,6 @@ public class MainActivity extends AppCompatActivity { handler.postDelayed(this, 10000); // 10秒后再次执行 } }, 5000); - - // 设置暂停/继续按钮点击事件 - // btnPauseResume.setOnClickListener(new View.OnClickListener() { - // @Override - // public void onClick(View v) { - // boolean isPaused = animationManager.togglePause(); - // btnPauseResume.setText(isPaused ? "继续" : "暂停"); - // } - // }); - - // // 设置切换表情按钮点击事件 - // btnSwitchFace.setOnClickListener(new View.OnClickListener() { - // @Override - // public void onClick(View v) { - // String faceName = animationManager.switchToNextFace(); - // tvCurrentFace.setText(faceName); - // Toast.makeText(MainActivity.this, "已切换到: " + faceName, Toast.LENGTH_SHORT).show(); - // } - // }); - - // // 设置速度调节滑块变化事件 - // seekBarSpeed.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { - // @Override - // public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - // if (fromUser) { - // // 将进度值(0-100)转换为速度因子(0.5-2.0) - // float speedFactor = 0.5f + (progress / 100.0f) * 1.5f; - // animationManager.setSpeed(speedFactor); - // tvSpeed.setText(String.format("%.1fx", speedFactor)); - // } - // } - - // @Override - // public void onStartTrackingTouch(SeekBar seekBar) { - // // 不需要实现 - // } - - // @Override - // public void onStopTrackingTouch(SeekBar seekBar) { - // // 不需要实现 - // } - // }); - - // // 初始化速度显示 - // tvSpeed.setText("1.0x"); - - // // 初始化表情名称显示 - // tvCurrentFace.setText(animationManager.getCurrentFaceName()); - -// mainLayout = findViewById(R.id.main_layout); -// mainLayout.setBackgroundColor(Color.BLACK); -// robotEyesView = new RobotEyesView(this); -// mainLayout.addView(robotEyesView); // 检查并请求必要的权限 checkAndRequestPermissions(); @@ -161,14 +102,15 @@ public class MainActivity extends AppCompatActivity { hideSystemBars(); // 初始化设备管理器 initDeviceManager(); + //testMotorControl(); // 延迟2秒后开始测试,确保设备管理器初始化完成 -// handler.postDelayed(new Runnable() { -// @Override -// public void run() { -// startProtocolTests(); -// } -// }, 2000); + handler.postDelayed(new Runnable() { + @Override + public void run() { + startProtocolTests(); + } + }, 2000); } private void initDeviceManager() { @@ -177,6 +119,11 @@ public class MainActivity extends AppCompatActivity { gpioManager.exportGpio(BT_POWER_EN); gpioManager.setDirection(BT_POWER_EN, GpioManager.DIRECTION_OUT); gpioManager.setValue(BT_POWER_EN, GpioManager.VALUE_HIGH); + + // 初始化电机控制器 + motorController = MotorController.getInstance(); + motorController.init(); + LogManager.d(TAG, "电机控制器初始化完成"); deviceManager = new ASR5515DeviceManager(this, DEVICE_PATH, BAUD_RATE); // 设置设备检查回调 @@ -315,14 +262,14 @@ public class MainActivity extends AppCompatActivity { // deviceManager.queryBtVersion(); // } // }, 2000); -// deviceManager.stopGH3220Measure(ASR5515Protocol.GH3220MeasureType.HR); -// deviceManager.stopGH3220Measure(ASR5515Protocol.GH3220MeasureType.SPO2); -// handler.postDelayed(new Runnable() { -// @Override -// public void run() { -// deviceManager.startGH3220Measure(ASR5515Protocol.GH3220MeasureType.SPO2); -// } -// }, 1000); + deviceManager.stopGH3220Measure(ASR5515Protocol.GH3220MeasureType.HR); + deviceManager.stopGH3220Measure(ASR5515Protocol.GH3220MeasureType.SPO2); +// handler.postDelayed(new Runnable() { +// @Override +// public void run() { +// deviceManager.startGH3220Measure(ASR5515Protocol.GH3220MeasureType.HR); +// } +// }, 1000); handler.postDelayed(wearDetectionRunnable, 1000); } @@ -363,6 +310,16 @@ public class MainActivity extends AppCompatActivity { } gpioManager = null; } + + // 释放电机控制器资源 + if (motorController != null) { + try { + motorController.release(); + LogManager.d(TAG, "电机控制器资源已释放"); + } catch (Exception e) { + LogManager.e(TAG, "释放电机控制器资源失败: " + e.getMessage()); + } + } } catch (Exception e) { LogManager.e(TAG, "资源释放过程中发生错误: " + e.getMessage()); } @@ -375,26 +332,15 @@ public class MainActivity extends AppCompatActivity { * 随机播放一个表情动画 */ private void playRandomFaceAnimation() { - // 可用的表情动画类型数组 - int[] faceAnimations = { - RobotFaceAnimationManager.FACE_LOOP_0, - RobotFaceAnimationManager.FACE_LOOP_1, - RobotFaceAnimationManager.FACE_LOOP_2, - RobotFaceAnimationManager.FACE_LOOP_3, - RobotFaceAnimationManager.FACE_LOOP_4, - RobotFaceAnimationManager.FACE_HAPPY, - RobotFaceAnimationManager.FACE_SAD - }; - // 随机选择一个表情 - int randomIndex = (int)(Math.random() * faceAnimations.length); - int selectedAnimation = faceAnimations[randomIndex]; + int selectedAnimation = (int)(Math.random() * 5); LogManager.d(TAG, "随机选择表情动画: " + selectedAnimation); // 加载选中的动画 if (animationManager != null) { animationManager.loadFaceAnimation(selectedAnimation); + animationManager.setSpeed(0.4f); } else { LogManager.e(TAG, "动画管理器未初始化"); } @@ -419,18 +365,91 @@ private void hideSystemBars() { } } -@Override -public void onWindowFocusChanged(boolean hasFocus) { - super.onWindowFocusChanged(hasFocus); - if (hasFocus) { - hideSystemBars(); - } -} - /** - * 检查并请求必要的权限 + * 测试电机控制功能 */ -private void checkAndRequestPermissions() { +private void testMotorControl() { + if (motorController == null) { + LogManager.e(TAG, "电机控制器未初始化"); + return; + } + + new Thread(() -> { + try { + // 打开电机 + LogManager.d(TAG, "打开电机"); + if (!motorController.turnOnMotor()) { + LogManager.e(TAG, "打开电机失败"); + return; + } + // 设置PWM参数 + final int period = 1000000; + final int dutyCycle = 1000000; // 使用90%的占空比,避免duty_cycle等于period + + Thread.sleep(10000); +// LogManager.d(TAG, "电机正转"); +// motorController.rotateReverse(period, 0); // 确保反转PWM关闭 +// if (!motorController.rotateForward(period, dutyCycle)) { +// LogManager.e(TAG, "电机正转失败"); +// } + // 循环20次正反转 + for (int i = 0; i < 100; i++) { + LogManager.d(TAG, "开始第" + (i + 1) + "次正反转测试"); + + // 电机正转 + LogManager.d(TAG, "电机正转"); + motorController.rotateReverse(period, 0); // 确保反转PWM关闭 + if (!motorController.rotateForward(period, dutyCycle)) { + LogManager.e(TAG, "电机正转失败"); + break; + } + + // 等待100ms + Thread.sleep(100); + + // 停止正转 + motorController.rotateForward(period, 0); + + // 电机反转 + LogManager.d(TAG, "电机反转"); + if (!motorController.rotateReverse(period, dutyCycle)) { + LogManager.e(TAG, "电机反转失败"); + break; + } + + // 等待100ms + Thread.sleep(100); + + // 停止反转 + motorController.rotateReverse(period, 0); + } + + // 测试完成,关闭电机 + LogManager.d(TAG, "电机测试完成,关闭电机"); + motorController.turnOffMotor(); + + } catch (InterruptedException e) { + LogManager.e(TAG, "电机测试被中断: " + e.getMessage()); + motorController.turnOffMotor(); + } catch (Exception e) { + LogManager.e(TAG, "电机测试异常: " + e.getMessage()); + motorController.turnOffMotor(); + } + }).start(); + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + if (hasFocus) { + hideSystemBars(); + } + } + + /** + * 检查并请求必要的权限 + */ + private void checkAndRequestPermissions() { // 检查是否已经有所需的权限 boolean allPermissionsGranted = true; for (String permission : REQUIRED_PERMISSIONS) { @@ -446,32 +465,32 @@ private void checkAndRequestPermissions() { } } -/** - * 处理权限请求结果 - */ -@Override -public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - - if (requestCode == PERMISSION_REQUEST_CODE) { - boolean allGranted = true; - for (int result : grantResults) { - if (result != PackageManager.PERMISSION_GRANTED) { - allGranted = false; - break; + /** + * 处理权限请求结果 + */ + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + + if (requestCode == PERMISSION_REQUEST_CODE) { + boolean allGranted = true; + for (int result : grantResults) { + if (result != PackageManager.PERMISSION_GRANTED) { + allGranted = false; + break; + } + } + + if (allGranted) { + // 所有权限都已授予,可以继续操作 + LogManager.d(TAG, "所有必要权限已授予"); + + } else { + // 有些权限被拒绝,显示提示并可能影响功能 + Toast.makeText(this, "需要所有请求的权限才能正常运行应用", Toast.LENGTH_LONG).show(); + LogManager.e(TAG, "部分权限被拒绝,可能影响应用功能"); } } - - if (allGranted) { - // 所有权限都已授予,可以继续操作 - LogManager.d(TAG, "所有必要权限已授予"); - - } else { - // 有些权限被拒绝,显示提示并可能影响功能 - Toast.makeText(this, "需要所有请求的权限才能正常运行应用", Toast.LENGTH_LONG).show(); - LogManager.e(TAG, "部分权限被拒绝,可能影响应用功能"); - } } -} } \ No newline at end of file diff --git a/app/src/main/java/com/ismart/ib86/app/RobotFaceAnimationManager.java b/app/src/main/java/com/ismart/ib86/app/RobotFaceAnimationManager.java index 7ac6364..943e5ff 100644 --- a/app/src/main/java/com/ismart/ib86/app/RobotFaceAnimationManager.java +++ b/app/src/main/java/com/ismart/ib86/app/RobotFaceAnimationManager.java @@ -17,13 +17,13 @@ import androidx.core.content.ContextCompat; public class RobotFaceAnimationManager { // 动画类型常量 - public static final int FACE_LOOP_0 = 1; // 循环表情0 - public static final int FACE_LOOP_1 = 2; // 循环表情1 - public static final int FACE_LOOP_2 = 3; // 循环表情2 - public static final int FACE_LOOP_3 = 4; // 循环表情3 - public static final int FACE_LOOP_4 = 5; // 循环表情4 - public static final int FACE_HAPPY = 6; // 开心表情 - public static final int FACE_SAD = 7; // 悲伤表情 + public static final int FACE_LOOP_0 = 0; // 循环表情0 + public static final int FACE_LOOP_1 = 1; // 循环表情1 + public static final int FACE_LOOP_2 = 2; // 循环表情2 + public static final int FACE_LOOP_3 = 3; // 循环表情3 + public static final int FACE_LOOP_4 = 4; // 循环表情4 + public static final int FACE_HAPPY = 5; // 开心表情 + public static final int FACE_SAD = 6; // 悲伤表情 // 动画资源ID数组 private static final int[] FACE_ANIMATIONS = { @@ -98,9 +98,9 @@ public class RobotFaceAnimationManager { } // 应用速度设置 - if (speedFactor != 1.0f) { - applySpeed(speedFactor); - } +// if (speedFactor != 1.0f) { +// applySpeed(speedFactor); +// } // 设置动画到ImageView imageView.setImageDrawable(currentAnimation); diff --git a/app/src/main/java/com/ismart/ib86/feature/motor/MotorController.java b/app/src/main/java/com/ismart/ib86/feature/motor/MotorController.java new file mode 100644 index 0000000..6550c05 --- /dev/null +++ b/app/src/main/java/com/ismart/ib86/feature/motor/MotorController.java @@ -0,0 +1,249 @@ +package com.ismart.ib86.feature.motor; + +import android.util.Log; + +import com.ismart.ib86.common.utils.LogManager; +import com.ismart.ib86.feature.gpio.GpioManager; + +import java.io.FileWriter; +import java.io.IOException; + +/** + * 电机控制器类,用于控制机器人抬头点头的电机 + * 采用单例模式确保全局只有一个控制实例 + */ +public class MotorController { + private static final String TAG = "MotorController"; + private static volatile MotorController instance; + + // GPIO控制常量 + private static final int MOTOR_CONTROL_GPIO = 59; // 电机开关GPIO + private static final int MOTOR_SLEEP_GPIO = 65; + private static final int GPIO_HIGH = 1; // GPIO高电平 + private static final int GPIO_LOW = 0; // GPIO低电平 + + // PWM路径常量 + private static final String PWM_FORWARD_PATH = "/sys/class/pwm/pwmchip0/pwm0/"; + private static final String PWM_REVERSE_PATH = "/sys/class/pwm/pwmchip1/pwm0/"; + private static final String PWM_PERIOD_FILE = "period"; + private static final String PWM_DUTY_CYCLE_FILE = "duty_cycle"; + private static final String PWM_ENABLE_FILE = "enable"; + + // 电机状态枚举 + public enum MotorState { + STOPPED, // 停止状态 + FORWARD, // 正转状态 + REVERSE // 反转状态 + } + + // 当前电机状态 + private MotorState currentState = MotorState.STOPPED; + + /** + * 获取MotorController单例实例 + * @return MotorController实例 + */ + public static MotorController getInstance() { + if (instance == null) { + synchronized (MotorController.class) { + if (instance == null) { + instance = new MotorController(); + } + } + } + return instance; + } + + /** + * 私有构造函数,防止外部实例化 + */ + private MotorController() { + // 私有构造函数 + } + + /** + * 初始化电机控制器 + * @return 是否初始化成功 + */ + public boolean init() { + try { + // 初始化GPIO + GpioManager.getInstance().setDirection(MOTOR_CONTROL_GPIO, GpioManager.DIRECTION_OUT); + // 默认关闭电机 + return turnOffMotor(); + } catch (Exception e) { + Log.e(TAG, "Failed to initialize motor controller", e); + return false; + } + } + + /** + * 打开电机 + * @return 是否成功 + */ + public boolean turnOnMotor() { + try { + rotateForward(1000000, 0); // 确保正转PWM关闭 + enablePwm(PWM_FORWARD_PATH, false); + enablePwm(PWM_FORWARD_PATH, true); + rotateReverse(1000000, 0); // 确保反转PWM关闭 + enablePwm(PWM_REVERSE_PATH, true); + boolean state = GpioManager.getInstance().setValue(MOTOR_CONTROL_GPIO, GPIO_HIGH); + // 设置SLEEP + GpioManager.getInstance().setDirection(MOTOR_SLEEP_GPIO, GpioManager.DIRECTION_OUT); + GpioManager.getInstance().setValue(MOTOR_SLEEP_GPIO, GpioManager.VALUE_HIGH); + return state; + } catch (Exception e) { + Log.e(TAG, "Failed to turn on motor", e); + return false; + } + } + + /** + * 关闭电机 + * @return 是否成功 + */ + public boolean turnOffMotor() { + try { + // 根据当前状态将相应PWM通道的参数设为0 + String pwmPath; + switch (currentState) { + case FORWARD: + pwmPath = PWM_FORWARD_PATH; + break; + case REVERSE: + pwmPath = PWM_REVERSE_PATH; + break; + case STOPPED: + default: + // 如果已经是停止状态,直接关闭GPIO + return GpioManager.getInstance().setValue(MOTOR_CONTROL_GPIO, GPIO_LOW); + } + + // 将period和dutyCycle设为0 + setPwmParameters(pwmPath, 1000000, 0); + + // 关闭电机控制GPIO + boolean result = GpioManager.getInstance().setValue(MOTOR_CONTROL_GPIO, GPIO_LOW); + + // 更新状态为停止 + currentState = MotorState.STOPPED; + + return result; + } catch (Exception e) { + Log.e(TAG, "Failed to turn off motor", e); + return false; + } + } + + /** + * 控制电机正转 + * @param period PWM周期(纳秒) + * @param dutyCycle PWM占空比(纳秒) + * @return 是否成功 + */ + public boolean rotateForward(int period, int dutyCycle) { + if (setPwmParameters(PWM_FORWARD_PATH, period, dutyCycle)) { + currentState = MotorState.FORWARD; + return true; + } + return false; + } + + /** + * 控制电机反转 + * @param period PWM周期(纳秒) + * @param dutyCycle PWM占空比(纳秒) + * @return 是否成功 + */ + public boolean rotateReverse(int period, int dutyCycle) { + if (setPwmParameters(PWM_REVERSE_PATH, period, dutyCycle)) { + currentState = MotorState.REVERSE; + return true; + } + return false; + } + + /** + * 设置PWM参数 + * @param basePath PWM基础路径 + * @param period 周期 + * @param dutyCycle 占空比 + * @return 是否成功 + */ + private boolean setPwmParameters(String basePath, int period, int dutyCycle) { + try { + //enablePwm(basePath, false); + writeToFile(basePath + PWM_PERIOD_FILE, String.valueOf(period)); + writeToFile(basePath + PWM_DUTY_CYCLE_FILE, String.valueOf(dutyCycle)); + // 使能PWM + //enablePwm(basePath, true); + return true; + } catch (IOException e) { + Log.e(TAG, "Failed to set PWM parameters", e); + return false; + } + } + + /** + * 写入文件 + * @param filePath 文件路径 + * @param content 内容 + * @throws IOException IO异常 + */ + private void writeToFile(String filePath, String content) throws IOException { + FileWriter writer = null; + try { + writer = new FileWriter(filePath); + writer.write(content); + } finally { + if (writer != null) { + try { + writer.close(); + } catch (IOException e) { + Log.e(TAG, "Failed to close file writer", e); + } + } + } + } + + /** + * 控制PWM使能状态 + * @param basePath PWM基础路径 + * @param enable true表示使能,false表示禁用 + * @return 是否成功 + */ + public boolean enablePwm(String basePath, boolean enable) { + try { + writeToFile(basePath + PWM_ENABLE_FILE, enable ? "1" : "0"); + return true; + } catch (IOException e) { + Log.e(TAG, "Failed to " + (enable ? "enable" : "disable") + " PWM", e); + return false; + } + } + + /** + * 控制电机旋转到指定角度(预留API,暂不实现) + * @param angle 目标角度(度) + * @param speed 旋转速度(可选参数) + * @return 是否成功 + */ + public boolean rotateToAngle(float angle, float speed) { + // TODO: 实现角度控制功能 + Log.i(TAG, "rotateToAngle API called, but not implemented yet"); + return false; + } + + /** + * 释放资源 + */ + public void release() { + try { + // 关闭电机 + turnOffMotor(); + } catch (Exception e) { + Log.e(TAG, "Failed to release resources", e); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ismart/ib86/feature/serial/SerialPort/ASR5515Protocol.java b/app/src/main/java/com/ismart/ib86/feature/serial/SerialPort/ASR5515Protocol.java index 7e323df..eb61be8 100644 --- a/app/src/main/java/com/ismart/ib86/feature/serial/SerialPort/ASR5515Protocol.java +++ b/app/src/main/java/com/ismart/ib86/feature/serial/SerialPort/ASR5515Protocol.java @@ -199,7 +199,6 @@ public class ASR5515Protocol { public static final short CMD_HOST_STATUS = 0x0079; // 主机状态同步请求 public static final short CMD_HOST_STATUS_RESP = 0x007A; // 主机状态同步响应 public static final short CMD_HOST_STATUS_BOOT_RESP = 0x007C; //主机状态,启动状态 - // 新增命令 public static final short CMD_BOOT_BIN_CHECK = 0x007D; // boot bin检查请求 [125 0 0] public static final short CMD_BOOT_BIN_CHECK_RESP = 0x007E; // boot bin检查响应 [126 sn len 0/1] public static final short CMD_COLLECT_FREQ_SET = 0x00C9; // 采集频率设置 [201 0 len 32] @@ -240,7 +239,7 @@ public class ASR5515Protocol { } public static byte[] createFrame(short command, byte[] data) { - LogManager.d(TAG, "LEN :" + data.length); + //LogManager.d(TAG, "LEN :" + data.length); int frameLength = 1 + 2 + 2 + 2 + data.length + 1; // start + command(2bytes) + sn(2bytes) + len(2bytes) + data + end ByteBuffer buffer = ByteBuffer.allocate(frameLength); @@ -319,7 +318,7 @@ public class ASR5515Protocol { System.arraycopy(data, startIndex + totalFrameLength, frame.remainingData, 0, frame.remainingData.length); } - LogManager.d(TAG, "Frame parsed - type: " + type + ", len: " + len + ", data length: " + frameData.length); + //LogManager.d(TAG, "Frame parsed - type: " + type + ", len: " + len + ", data length: " + frameData.length); return frame; } catch (Exception e) { LogManager.e(TAG, "Parse frame error: " + e.getMessage()); diff --git a/app/src/main/res/layout/activity_robot_face.xml b/app/src/main/res/layout/activity_robot_face.xml index e41dcb0..5761e76 100644 --- a/app/src/main/res/layout/activity_robot_face.xml +++ b/app/src/main/res/layout/activity_robot_face.xml @@ -11,152 +11,14 @@ + android:gravity="center" + android:layout_marginTop="-150dp"> - - - \ No newline at end of file