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 55737b9..96053b5 100644 --- a/app/src/main/java/com/ismart/ib86/app/MainActivity.java +++ b/app/src/main/java/com/ismart/ib86/app/MainActivity.java @@ -2,6 +2,7 @@ package com.ismart.ib86.app; import android.Manifest; import android.content.pm.PackageManager; +import android.graphics.Color; import android.os.Bundle; import android.os.Handler; import android.view.Window; @@ -10,6 +11,7 @@ import android.view.WindowInsetsController; import android.os.Build; import android.view.View; import android.util.Log; +import android.widget.RelativeLayout; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; @@ -24,10 +26,11 @@ 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.view.RobotEyesView; public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; - private static final String DEVICE_PATH = "/dev/ttyS2"; + private static final String DEVICE_PATH = "/dev/ttyS3"; private static final int BAUD_RATE = 921600; // 权限请求码 @@ -40,15 +43,38 @@ public class MainActivity extends AppCompatActivity { private static final int BT_POWER_EN = 75; private ASR5515DeviceManager deviceManager; - private Handler handler = new Handler(); - private GpioTest gpioTest; + // 使用弱引用避免内存泄漏 + private Handler handler = new Handler(new Handler.Callback() { + @Override + public boolean handleMessage(@NonNull android.os.Message msg) { + return false; + } + }); + private Runnable wearDetectionRunnable = new Runnable() { + @Override + public void run() { + // 发送佩戴检测请求 + deviceManager.sendWearDetectionRequest(); + + // 1秒后再次执行 + handler.postDelayed(this, 1000); + } +}; private GpioManager gpioManager; + private RelativeLayout mainLayout; + private RobotEyesView robotEyesView; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); + mainLayout = findViewById(R.id.main_layout); + mainLayout.setBackgroundColor(Color.BLACK); + robotEyesView = new RobotEyesView(this); + mainLayout.addView(robotEyesView); + // 检查并请求必要的权限 checkAndRequestPermissions(); @@ -210,39 +236,50 @@ 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.sendWearDetectionRequest(); +// deviceManager.startGH3220Measure(ASR5515Protocol.GH3220MeasureType.SPO2); // } -// }, 4000); +// }, 1000); + + handler.postDelayed(wearDetectionRunnable, 1000); } @Override protected void onDestroy() { super.onDestroy(); - handler.removeCallbacksAndMessages(null); + // 移除所有Handler消息和回调,避免内存泄漏 + if (handler != null) { + handler.removeCallbacksAndMessages(null); + handler = null; + } // 移至后台线程执行耗时操作,避免ANR new Thread(() -> { -// if (deviceManager != null) { -// deviceManager.syncHostStatus(false); -// deviceManager.close(); -// } - - // 释放GPIO测试资源 - if (gpioTest != null) { - gpioTest.release(); - gpioTest = null; + try { + // 停止所有测量 + if (deviceManager != null) { + deviceManager.stopGH3220Measure(ASR5515Protocol.GH3220MeasureType.HR); + deviceManager.stopGH3220Measure(ASR5515Protocol.GH3220MeasureType.SPO2); + deviceManager.syncHostStatus(false); + deviceManager.close(); + deviceManager = null; + } + + // 关闭GPIO电源 + if (gpioManager != null) { + try { + gpioManager.setValue(BT_POWER_EN, GpioManager.VALUE_LOW); + } catch (Exception e) { + LogManager.e(TAG, "关闭GPIO电源失败: " + e.getMessage()); + } + gpioManager = null; + } + } catch (Exception e) { + LogManager.e(TAG, "资源释放过程中发生错误: " + e.getMessage()); } }).start(); } diff --git a/app/src/main/java/com/ismart/ib86/feature/serial/SerialPort/ASR5515DeviceManager.java b/app/src/main/java/com/ismart/ib86/feature/serial/SerialPort/ASR5515DeviceManager.java index 76860d8..a7a7279 100644 --- a/app/src/main/java/com/ismart/ib86/feature/serial/SerialPort/ASR5515DeviceManager.java +++ b/app/src/main/java/com/ismart/ib86/feature/serial/SerialPort/ASR5515DeviceManager.java @@ -13,11 +13,47 @@ import com.ismart.ib86.feature.gpio.GpioManager; */ public class ASR5515DeviceManager { private static final String TAG = "ASR5515DeviceManager"; + + // 设备状态标志 + private volatile boolean isDeviceOpen = false; + private final Object sendLock = new Object(); // 发送数据的同步锁 + + /** + * 检查设备是否就绪 + * @return true 如果设备已打开且就绪 + */ + private boolean isDeviceReady() { + return isDeviceOpen && serialPortHelper != null; + } + + /** + * 安全发送数据的方法 + * @param data 要发送的数据 + * @return true 如果发送成功 + */ + protected boolean safeSendData(byte[] data) { + if (!isDeviceReady()) { + LogManager.e(TAG, "设备未就绪,无法发送数据"); + return false; + } + + synchronized (sendLock) { + try { + serialPortHelper.sendAsync(data); + return true; + } catch (Exception e) { + LogManager.e(TAG, "发送数据失败: " + e.getMessage()); + return false; + } + } + } private static final int GPIO_BT_WAKE = 33; // 蓝牙唤醒GPIO private final SerialPortHelper serialPortHelper; private final Handler mainHandler; private final GpioManager gpioManager; + private final Object gpioLock = new Object(); // GPIO操作的同步锁 + private volatile boolean isInitialized = false; // 初始化状态标志 // 细分回调实例 private DeviceCheckListener deviceCheckListener; private DeviceControlListener deviceControlListener; @@ -106,16 +142,36 @@ public class ASR5515DeviceManager { * @param context 上下文 * @param devicePath 设备路径 * @param baudRate 波特率 + * @throws IllegalStateException 如果串口打开失败 */ public ASR5515DeviceManager(Context context, String devicePath, int baudRate) { - serialPortHelper = new SerialPortHelper(devicePath, baudRate); - mainHandler = new Handler(Looper.getMainLooper()); - gpioManager = GpioManager.getInstance(); - serialPortHelper.setSimulationMode(false); - // 初始化串口 - boolean result = serialPortHelper.open(); - if (!result) { - LogManager.e(TAG, "Failed to open serial port: " + devicePath); + synchronized (sendLock) { + try { + serialPortHelper = new SerialPortHelper(devicePath, baudRate); + mainHandler = new Handler(Looper.getMainLooper()); + gpioManager = GpioManager.getInstance(); + serialPortHelper.setSimulationMode(false); + + // 初始化串口 + boolean result = serialPortHelper.open(); + if (!result) { + String errorMsg = "Failed to open serial port: " + devicePath; + LogManager.e(TAG, errorMsg); + updateDeviceState(false); + isInitialized = false; + // 记录错误但不抛出异常,允许应用继续运行 + // 在实际使用时会检查isDeviceOpen状态 + } else { + updateDeviceState(true); + isInitialized = true; + LogManager.i(TAG, "Serial port opened successfully: " + devicePath); + } + } catch (Exception e) { + LogManager.e(TAG, "Device initialization failed: " + e.getMessage()); + updateDeviceState(false); + isInitialized = false; + throw new IllegalStateException("Failed to initialize device manager", e); + } } // 设置设备状态回调 @@ -346,28 +402,72 @@ public class ASR5515DeviceManager { } /** - * 检查设备状态 + * 检查设备是否已打开且可用 + * @return true 如果设备已打开且可用 */ - public void checkDevice() { - wakeupDevice(); - serialPortHelper.checkDevice(); + public boolean isDeviceAvailable() { + return isDeviceReady(); + } + + /** + * 检查设备状态 + * @return true 如果命令发送成功 + */ + public boolean checkDevice() { + if (!isDeviceReady()) { + LogManager.e(TAG, "设备未就绪,无法检查设备状态"); + return false; + } + + try { + wakeupDevice(); + serialPortHelper.checkDevice(); + return true; + } catch (Exception e) { + LogManager.e(TAG, "检查设备状态失败: " + e.getMessage()); + return false; + } } /** * 重启设备 + * @return true 如果命令发送成功 */ - public void restartDevice() { - wakeupDevice(); - serialPortHelper.restartDevice(); + public boolean restartDevice() { + if (!isDeviceReady()) { + LogManager.e(TAG, "设备未就绪,无法重启设备"); + return false; + } + + try { + wakeupDevice(); + serialPortHelper.restartDevice(); + return true; + } catch (Exception e) { + LogManager.e(TAG, "重启设备失败: " + e.getMessage()); + return false; + } } /** * 控制日志输出 * @param enable 是否启用 + * @return true 如果命令发送成功 */ - public void setLogControl(boolean enable) { - wakeupDevice(); - serialPortHelper.setLogControl(enable); + public boolean setLogControl(boolean enable) { + if (!isDeviceReady()) { + LogManager.e(TAG, "设备未就绪,无法控制日志输出"); + return false; + } + + try { + wakeupDevice(); + serialPortHelper.setLogControl(enable); + return true; + } catch (Exception e) { + LogManager.e(TAG, "控制日志输出失败: " + e.getMessage()); + return false; + } } /** @@ -396,10 +496,31 @@ public class ASR5515DeviceManager { } /** - * 关闭设备 + * 设备状态变化的回调接口 */ - public void close() { - serialPortHelper.close(); + public interface DeviceStateCallback { + void onDeviceStateChanged(boolean isOpen); + } + + private DeviceStateCallback deviceStateCallback; + + /** + * 设置设备状态变化回调 + * @param callback 回调接口 + */ + public void setDeviceStateCallback(DeviceStateCallback callback) { + this.deviceStateCallback = callback; + } + + /** + * 更新设备状态并通知回调 + * @param newState 新的设备状态 + */ + private void updateDeviceState(boolean newState) { + isDeviceOpen = newState; + if (deviceStateCallback != null) { + mainHandler.post(() -> deviceStateCallback.onDeviceStateChanged(newState)); + } } /** @@ -473,24 +594,85 @@ public class ASR5515DeviceManager { /** * 唤醒设备 * 通过GPIO33唤醒蓝牙,ASR休眠时会自动发送'w'字符 + * 使用同步锁确保GPIO操作的线程安全 */ public void wakeupDevice() { - try { - // 配置GPIO33为输出模式 - gpioManager.exportGpio(GPIO_BT_WAKE); - gpioManager.setDirection(GPIO_BT_WAKE, GpioManager.DIRECTION_OUT); - - // 拉低GPIO33 - gpioManager.setValue(GPIO_BT_WAKE, 0); - - // 等待30ms - Thread.sleep(30); - - // 拉高GPIO33 - gpioManager.setValue(GPIO_BT_WAKE, 1); - - } catch (Exception e) { - LogManager.e(TAG, "Failed to wakeup bluetooth: " + e.getMessage()); + synchronized (gpioLock) { + try { + // 配置GPIO33为输出模式 + gpioManager.exportGpio(GPIO_BT_WAKE); + gpioManager.setDirection(GPIO_BT_WAKE, GpioManager.DIRECTION_OUT); + + // 拉低GPIO33 + gpioManager.setValue(GPIO_BT_WAKE, 0); + + // 等待30ms + Thread.sleep(30); + + // 拉高GPIO33 + gpioManager.setValue(GPIO_BT_WAKE, 1); + + //LogManager.d(TAG, "设备唤醒成功"); + } catch (InterruptedException e) { + //LogManager.e(TAG, "设备唤醒被中断: " + e.getMessage()); + Thread.currentThread().interrupt(); // 重置中断状态 + } catch (Exception e) { + //LogManager.e(TAG, "设备唤醒失败: " + e.getMessage()); + // 尝试恢复GPIO状态 + try { + gpioManager.setValue(GPIO_BT_WAKE, 1); // 确保GPIO处于高电平 + } catch (Exception ex) { + //LogManager.e(TAG, "恢复GPIO状态失败: " + ex.getMessage()); + } + } } } + /** + * 关闭设备,释放所有资源 + */ + public void close() { + synchronized (sendLock) { + try { + // 清除所有回调,避免内存泄漏 + clearAllListeners(); + + // 关闭串口 + if (serialPortHelper != null) { + serialPortHelper.close(); + } + + // 清除所有Handler消息和回调 + if (mainHandler != null) { + mainHandler.removeCallbacksAndMessages(null); + } + + // 更新设备状态 + updateDeviceState(false); + isInitialized = false; + } catch (Exception e) { + LogManager.e(TAG, "Error closing device manager: " + e.getMessage()); + } + } + } + + /** + * 清除所有监听器,避免内存泄漏 + */ + private void clearAllListeners() { + deviceCheckListener = null; + deviceControlListener = null; + logControlListener = null; + btVersionListener = null; + btUpgradeListener = null; + hostStatusListener = null; + bootBinCheckListener = null; + collectFreqSetListener = null; + hrBpBoAutoMeasureListener = null; + hrBpBoManualMeasureListener = null; + dynamicMeasureListener = null; + wearDetectionListener = null; + gh3220MeasureListener = null; + algoResultListener = null; + } + } \ No newline at end of file diff --git a/app/src/main/java/com/ismart/ib86/feature/serial/SerialPort/SerialPortHelper.java b/app/src/main/java/com/ismart/ib86/feature/serial/SerialPort/SerialPortHelper.java index 408a00c..f2d9043 100644 --- a/app/src/main/java/com/ismart/ib86/feature/serial/SerialPort/SerialPortHelper.java +++ b/app/src/main/java/com/ismart/ib86/feature/serial/SerialPort/SerialPortHelper.java @@ -200,15 +200,6 @@ public class SerialPortHelper { this.algoResultCallback = callback; } - /** - * 发送佩戴检测查询请求 - */ - public void sendWearDetectionRequest() { - byte[] data = ASR5515Protocol.createWearDetectionRequest(); - sendData(data, null); - } - - // ASR-5515协议命令发送方法 public void checkDevice() { byte[] data = ASR5515Protocol.createDeviceCheckRequest(); sendData(data, null); @@ -306,6 +297,14 @@ public class SerialPortHelper { sendData(data, null); } + /** + * 发送佩戴检测查询请求 + */ + public void sendWearDetectionRequest() { + byte[] data = ASR5515Protocol.createWearDetectionRequest(); + sendData(data, null); + } + public boolean open() { try { LogManager.d(TAG, "Opening serial port: " + mPortName + " with baud rate: " + mBaudRate); diff --git a/app/src/main/java/com/ismart/ib86/view/RobotEyesView.java b/app/src/main/java/com/ismart/ib86/view/RobotEyesView.java new file mode 100644 index 0000000..e03f239 --- /dev/null +++ b/app/src/main/java/com/ismart/ib86/view/RobotEyesView.java @@ -0,0 +1,78 @@ +package com.ismart.ib86.view; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.util.AttributeSet; +import android.view.View; +import android.view.animation.Animation; +import android.view.animation.Transformation; + +public class RobotEyesView extends View { + + private Paint paint; + private float eyeWidth = 100f; // Increased size for eyes + private float eyeHeight = 100f; // Increased size for eyes + private float borderRadius = 25f; // Adjusted border radius for larger eyes + private float blinkDuration = 3000f; // 3 seconds in milliseconds + + public RobotEyesView(Context context) { + super(context); + init(); + } + + public RobotEyesView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public RobotEyesView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + private void init() { + paint = new Paint(); + paint.setColor(Color.rgb(26, 249, 248)); + paint.setStyle(Paint.Style.FILL); + + startBlinkAnimation(); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + float centerXLeft = getWidth() / 4f; + float centerXRight = getWidth() * 3 / 4f; + float centerY = getHeight() / 3f; // Set eyes to one-third of screen height + + drawEye(canvas, centerXLeft, centerY); + drawEye(canvas, centerXRight, centerY); + } + + private void drawEye(Canvas canvas, float centerX, float centerY) { + canvas.drawRoundRect(centerX - eyeWidth / 2, centerY - eyeHeight / 2, + centerX + eyeWidth / 2, centerY + eyeHeight / 2, + borderRadius, borderRadius, paint); + } + + private void startBlinkAnimation() { + final Animation animation = new Animation() { + @Override + protected void applyTransformation(float interpolatedTime, Transformation t) { + if (interpolatedTime >= 0.2 && interpolatedTime < 0.25) { + eyeHeight = 20f; // Adjusted size for blinking effect + } else if (interpolatedTime >= 0.25 && interpolatedTime < 0.3) { + eyeHeight = 100f; // Reset to original size + } + invalidate(); + } + }; + + animation.setDuration((long) blinkDuration); + animation.setRepeatCount(Animation.INFINITE); + startAnimation(animation); + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_asr5515_demo.xml b/app/src/main/res/layout/activity_asr5515_demo.xml deleted file mode 100644 index 5c7b45d..0000000 --- a/app/src/main/res/layout/activity_asr5515_demo.xml +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - - -