diff --git a/.gitignore b/.gitignore index a8b0d1d..aa724b7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,35 +1,15 @@ -# ---> Android -# Gradle files -.gradle/ -build/ - -# Local configuration file (sdk path, etc) -local.properties - -# Log/OS Files -*.log - -# Android Studio generated files and folders -captures/ -.externalNativeBuild/ -.cxx/ -*.apk -output.json - -# IntelliJ *.iml -.idea/ -misc.xml -deploymentTargetDropDown.xml -render.experimental.xml - -# Keystore files -*.jks -*.keystore - -# Google Services (e.g. APIs or Firebase) -google-services.json - -# Android Profiling -*.hprof - +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..eaf91e2 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..8b3f102 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml new file mode 100644 index 0000000..0d46093 --- /dev/null +++ b/.idea/deploymentTargetSelector.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..863b1be --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/migrations.xml b/.idea/migrations.xml new file mode 100644 index 0000000..48052b2 --- /dev/null +++ b/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..ae3bf2d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..5bd6771 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index fbbeba0..e0f5ca5 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,146 @@ -# iB86 - +# ASR5515串口通信Android应用 + +## 项目概述 +这是一个用于与ASR5515设备进行串口通信的Android应用,提供设备管理、状态监控、固件升级等功能。应用通过串口(/dev/ttyS3)与设备通信,波特率115200。 + +## 主要功能 +- 设备检查与状态监控 +- 设备重启 +- 日志控制 +- 蓝牙版本查询 +- 蓝牙固件升级 +- 主机状态同步 +- 心率血压血氧(HR/BP/BO)测量 + - 自动测量 + - 手动测量 +- 动态测量功能 +- Boot bin检查 +- 采集频率设置 +- 支持模拟模式(不实际连接硬件) + +## 项目结构 +``` +com.ismart.ib86 +├── app/ # 主应用相关 +│ └── MainActivity.java +├── common/ # 公共组件 +│ └── utils/ # 工具类 +└── feature/ # 功能模块 + └── serial/ # 串口通信实现 + ├── ASR5515DeviceManager.java + ├── ASR5515Protocol.java + └── SerialPortHelper.java +``` + +## 核心类说明 + +### 1. ASR5515Protocol +协议核心类,定义ASR5515设备的通信协议格式和命令。 + +**主要功能:** +- 定义设备信息结构(DeviceInfo) +- 定义各种命令(Commands) +- 定义帧结构(Frame) +- 提供各种请求和响应的创建与解析方法 + +### 2. SerialPortHelper +串口通信核心实现类,封装了与ASR5515设备的通信逻辑。 + +**主要功能:** +- 串口初始化和管理 +- 数据发送和接收 +- ASR5515协议命令的封装 +- 回调机制处理各种响应 +- 支持模拟模式 + +### 3. ASR5515DeviceManager +设备管理核心类,提供高级设备管理接口。 + +**主要功能:** +- 设备状态检查 +- 设备重启 +- 日志控制 +- 蓝牙版本查询 +- 蓝牙升级 +- 主机状态同步 + +### 4. MainActivity +应用主界面,提供用户交互界面。 + +## 使用说明 + +### 初始化串口 +```java +SerialPortHelper serialPortHelper = new SerialPortHelper("/dev/ttyS1", 115200); +serialPortHelper.setDeviceStatusCallback(new DeviceStatusCallback() { + @Override + public void onDeviceCheckResponse(DeviceInfo deviceInfo) { + // 处理设备检查响应 + } + + // 实现其他回调方法... +}); +serialPortHelper.open(); +``` + +### 发送命令 +```java +// 设备检查 +serialPortHelper.checkDevice(); + +// 设备重启 +serialPortHelper.restartDevice(); + +// 查询蓝牙版本 +serialPortHelper.queryBtVersion(); + +// 蓝牙升级 +serialPortHelper.upgradeBt(); +``` + +### 关闭串口 +```java +serialPortHelper.close(); +``` + +## 开发环境要求 +- Android Studio 最新稳定版 +- Android SDK API 21+ +- JDK 8+ +- 支持串口的Android设备或模拟器 + +## 硬件连接指南 +1. 确保ASR5515设备已正确供电 +2. 使用USB转串口线连接设备与Android主机 +3. 确认串口设备节点(通常为/dev/ttyS3) +4. 设置波特率为115200 + +## 构建与运行 +1. 克隆项目 +```bash +git clone https://github.com/ismart/ib86.git +``` +2. 导入Android Studio +3. 配置设备连接参数(在MainActivity中修改) +4. 构建并运行应用 + +## 测试方法 +1. 单元测试 +```bash +./gradlew test +``` +2. 功能测试 +- 使用模拟模式测试基本功能 +- 连接实际设备测试通信功能 +- 测试各种测量功能(HR/BP/BO) + +## 依赖项 +- Android SerialPort API +- ASR5515通信协议(参考ASR-5515通信协议.docx) + +## 注意事项 +1. 需要设备支持串口通信 +2. 需要正确配置串口名称和波特率 +3. 模拟模式可用于开发测试 +4. 实际使用时请参考ASR5515设备的具体通信协议 +5. 测量功能需要ASR5515设备支持相应传感器 \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..8c6bdf4 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,49 @@ +plugins { + alias(libs.plugins.android.application) +} + +android { + namespace 'com.ismart.ib86.app' + compileSdk 34 + + defaultConfig { + applicationId "com.ismart.ib86.app" + minSdk 26 + targetSdk 34 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + ndk { + //ֵ֧SOܹ߿ԸҪѡһƽ̨so + abiFilters "armeabi-v7a" + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } + sourceSets { + main { + jniLibs.srcDir 'libs' + } + } +} + +dependencies { + + implementation libs.appcompat + implementation libs.material + implementation libs.activity + implementation libs.constraintlayout + testImplementation libs.junit + androidTestImplementation libs.ext.junit + androidTestImplementation libs.espresso.core +} \ No newline at end of file diff --git a/app/libs/armeabi-v7a/libserial_port.so b/app/libs/armeabi-v7a/libserial_port.so new file mode 100644 index 0000000..e4f57b6 Binary files /dev/null and b/app/libs/armeabi-v7a/libserial_port.so differ diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..209f24e --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/android/serialport/SerialPort.java b/app/src/main/java/android/serialport/SerialPort.java new file mode 100644 index 0000000..39da810 --- /dev/null +++ b/app/src/main/java/android/serialport/SerialPort.java @@ -0,0 +1,89 @@ +package android.serialport; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * 串口通信,打开串口,读写数据 + */ +public class SerialPort { + + private FileDescriptor mFd; + private FileInputStream mFileInputStream; + private FileOutputStream mFileOutputStream; + + static { + System.loadLibrary("serial_port"); + } + + /** + * 打开串口 + * + * @param device + * @param baudrate + */ + public SerialPort(File device, int baudrate) throws IOException { + if (!device.canRead() || !device.canWrite()) { + try { + Process su; + su = Runtime.getRuntime().exec("su"); + String cmd = "chmod 777 " + device.getAbsolutePath(); + su.getOutputStream().write(cmd.getBytes()); + su.getOutputStream().flush(); + int waitFor = su.waitFor(); + boolean canRead = device.canRead(); + boolean canWrite = device.canWrite(); + if (waitFor != 0 || !canRead || !canWrite) { + throw new SecurityException(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + mFd = open(device.getAbsolutePath(), baudrate); + + if (mFd == null) { + throw new IOException(); + } + mFileInputStream = new FileInputStream(mFd); + mFileOutputStream = new FileOutputStream(mFd); + } + + /** + * 关闭串口 + */ + public void closePort() { + if (this.mFd != null) { + try { + this.close(); + this.mFd = null; + this.mFileInputStream = null; + this.mFileOutputStream = null; + } catch (Exception var2) { + var2.printStackTrace(); + } + } + } + + public InputStream getInputStream() { + return mFileInputStream; + } + + public OutputStream getOutputStream() { + return mFileOutputStream; + } + + /** + * JNI,设备地址和波特率 + */ + private native static FileDescriptor open(String path, int baudrate); + + private native void close(); + + +} diff --git a/app/src/main/java/android/serialport/SerialPortFinder.java b/app/src/main/java/android/serialport/SerialPortFinder.java new file mode 100644 index 0000000..6107cb9 --- /dev/null +++ b/app/src/main/java/android/serialport/SerialPortFinder.java @@ -0,0 +1,125 @@ +/* + * Copyright 2009 Cedric Priscal + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.serialport; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.LineNumberReader; +import java.util.Iterator; +import java.util.Vector; + +public class SerialPortFinder { + + private static final String TAG = "info"; + private Vector mDrivers = null; + + Vector getDrivers() throws IOException { + if (mDrivers == null) { + mDrivers = new Vector(); + LineNumberReader r = new LineNumberReader(new FileReader("/proc/tty/drivers")); + String l; + while ((l = r.readLine()) != null) { + String[] w = l.split(" +"); + if ((w.length == 5) && ("serial".equals(w[4]))) { + mDrivers.add(new Driver(w[0], w[1])); + } + } + r.close(); + } + return mDrivers; + } + + /** + * 获取所有的设备,名字 + * + * @return + */ + public String[] getDevices() { + Vector devices = new Vector(); + Iterator itdriv; + try { + itdriv = getDrivers().iterator(); + while (itdriv.hasNext()) { + Driver driver = itdriv.next(); + Iterator itdev = driver.getDevices().iterator(); + while (itdev.hasNext()) { + String device = itdev.next().getName(); + String value = String.format("%s (%s)", device, driver.getName()); + devices.add(value); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + return devices.toArray(new String[devices.size()]); + } + + /** + * 获取所有设备的地址 + * + * @return + */ + public String[] getDevicesPaths() { + Vector devices = new Vector(); + Iterator itdriv; + try { + itdriv = getDrivers().iterator(); + while (itdriv.hasNext()) { + Driver driver = itdriv.next(); + Iterator itdev = driver.getDevices().iterator(); + while (itdev.hasNext()) { + String device = itdev.next().getAbsolutePath(); + devices.add(device); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + return devices.toArray(new String[devices.size()]); + } + + public class Driver { + Vector mDevices = null; + private String mDriverName; + private String mDeviceRoot; + + public Driver(String name, String root) { + mDriverName = name; + mDeviceRoot = root; + } + + public Vector getDevices() { + if (mDevices == null) { + mDevices = new Vector(); + File dev = new File("/dev"); + File[] files = dev.listFiles(); + int i; + for (i = 0; i < files.length; i++) { + if (files[i].getAbsolutePath().startsWith(mDeviceRoot)) { + mDevices.add(files[i]); + } + } + } + return mDevices; + } + + public String getName() { + return mDriverName; + } + } +} diff --git a/app/src/main/java/com/ismart/ib86/app/MainActivity.java b/app/src/main/java/com/ismart/ib86/app/MainActivity.java new file mode 100644 index 0000000..32042fb --- /dev/null +++ b/app/src/main/java/com/ismart/ib86/app/MainActivity.java @@ -0,0 +1,297 @@ +package com.ismart.ib86.app; + +import android.Manifest; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.os.Handler; +import android.view.Window; +import android.view.WindowInsets; +import android.view.WindowInsetsController; +import android.os.Build; +import android.view.View; +import android.util.Log; +import android.widget.Toast; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; + +import com.ismart.ib86.app.R; +import com.ismart.ib86.feature.serial.SerialPort.ASR5515Protocol; +import com.ismart.ib86.feature.serial.SerialPort.SerialPortHelper; +import com.ismart.ib86.feature.gpio.GpioManager; +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; + +public class MainActivity extends AppCompatActivity { + private static final String TAG = "MainActivity"; + private static final String DEVICE_PATH = "/dev/ttyS2"; + private static final int BAUD_RATE = 921600; + + // 权限请求码 + private static final int PERMISSION_REQUEST_CODE = 1001; + // 需要请求的权限 + private static final String[] REQUIRED_PERMISSIONS = { + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE + }; + + private static final int BT_POWER_EN = 75; + private ASR5515DeviceManager deviceManager; + private Handler handler = new Handler(); + private GpioTest gpioTest; + private GpioManager gpioManager; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + // 检查并请求必要的权限 + checkAndRequestPermissions(); + + // 隐藏状态栏和导航栏 + hideSystemBars(); + // 初始化设备管理器 + initDeviceManager(); + + // 延迟2秒后开始测试,确保设备管理器初始化完成 + handler.postDelayed(new Runnable() { + @Override + public void run() { + startProtocolTests(); + } + }, 2000); + } + + private void initDeviceManager() { + //打开5515电源 + gpioManager = GpioManager.getInstance(); + gpioManager.exportGpio(BT_POWER_EN); + gpioManager.setDirection(BT_POWER_EN, GpioManager.DIRECTION_OUT); + gpioManager.setValue(BT_POWER_EN, GpioManager.VALUE_HIGH); + + deviceManager = new ASR5515DeviceManager(this, DEVICE_PATH, BAUD_RATE); + // 设置设备检查回调 + deviceManager.setDeviceCheckListener(deviceInfo -> { + if (deviceInfo != null) { + LogManager.d(TAG, "设备检查成功: " + deviceInfo.toString()); + } else { + LogManager.e(TAG, "设备检查失败"); + } + }); + + // 设置设备控制回调 + deviceManager.setDeviceControlListener(new ASR5515DeviceManager.DeviceControlListener() { + @Override + public void onDeviceRestartResponse(boolean success) { + if (success) { + LogManager.d(TAG, "设备重启成功"); + } else { + LogManager.e(TAG, "设备重启失败"); + } + } + }); + + // 设置日志控制回调 + deviceManager.setLogControlListener(new ASR5515DeviceManager.LogControlListener() { + @Override + public void onLogControlResponse(boolean success) { + if (success) { + LogManager.d(TAG, "日志控制设置成功"); + } else { + LogManager.e(TAG, "日志控制设置失败"); + } + } + }); + + // 设置蓝牙版本回调 + deviceManager.setBtVersionListener(version -> { + if (version != null) { + LogManager.d(TAG, "蓝牙版本: " + version); + } else { + LogManager.e(TAG, "获取蓝牙版本失败"); + } + }); + + // 设置蓝牙升级回调 + deviceManager.setBtUpgradeListener(response -> + LogManager.d(TAG, "蓝牙升级: " + response.toString()) + ); + + // 设置主机状态回调 + deviceManager.setHostStatusListener(new ASR5515DeviceManager.HostStatusListener() { + @Override + public void onHostStatusResponse(ASR5515Protocol.HostStatusResponse response) { + LogManager.d(TAG, "主机状态: " + response.status); + } + }); + deviceManager.setBootBinCheckListener(new ASR5515DeviceManager.BootBinCheckListener() { + @Override + public void onBootBinCheckResponse(ASR5515Protocol.BootBinCheckResponse response) { + LogManager.d(TAG, "Boot bin检查: " + response.toString()); + } + }); + deviceManager.setCollectFreqSetListener(new ASR5515DeviceManager.CollectFreqSetListener() { + @Override + public void onCollectFreqSetResponse(ASR5515Protocol.CollectFreqSetResponse response) { + LogManager.d(TAG, "采集频率设置: " + response.toString()); + } + }); + + // 设置心率血压血氧自动测量回调 + deviceManager.setHrBpBoAutoMeasureListener(new ASR5515DeviceManager.HrBpBoAutoMeasureListener() { + @Override + public void onHrBpBoAutoMeasureResponse(ASR5515Protocol.HrBpBoAutoMeasureResponse response) { + LogManager.d(TAG, "HrBpBo自动测量: " + response.toString()); + } + }); + + // 设置心率血压血氧手动测量回调 + deviceManager.setHrBpBoManualMeasureListener(new ASR5515DeviceManager.HrBpBoManualMeasureListener() { + @Override + public void onHrBpBoManualMeasureResponse(ASR5515Protocol.HrBpBoManualMeasureResponse response) { + LogManager.d(TAG, "HrBpBo手动测量: " + response.toString()); + } + }); + + // 设置动态测量回调 + deviceManager.setDynamicMeasureListener(response -> + LogManager.d(TAG, "动态测量: " + response.toString()) + ); + + // 设置佩戴检测回调 + deviceManager.setWearDetectionListener(response -> + LogManager.d(TAG, "佩戴检测: " + response.isWearing) + ); + } + + private void startProtocolTests() { + LogManager.d(TAG, "开始协议测试..."); + +// handler.postDelayed(new Runnable() { +// @Override +// public void run() { +// deviceManager.checkDevice(); +// } +// }, 1000); +// +// handler.postDelayed(new Runnable() { +// @Override +// public void run() { +// deviceManager.queryBtVersion(); +// } +// }, 2000); + + handler.postDelayed(new Runnable() { + @Override + public void run() { + deviceManager.sendWearDetectionRequest(); + } + }, 1000); + +// handler.postDelayed(new Runnable() { +// @Override +// public void run() { +// deviceManager.sendWearDetectionRequest(); +// } +// }, 4000); + } + + @Override +protected void onDestroy() { + super.onDestroy(); + handler.removeCallbacksAndMessages(null); + + // 移至后台线程执行耗时操作,避免ANR + new Thread(() -> { + if (deviceManager != null) { + deviceManager.syncHostStatus(false); + deviceManager.close(); + } + + // 释放GPIO测试资源 + if (gpioTest != null) { + gpioTest.release(); + gpioTest = null; + } + }).start(); +} + +private void hideSystemBars() { + Window window = getWindow(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + WindowInsetsController insetsController = window.getInsetsController(); + if (insetsController != null) { + // 隐藏状态栏和导航栏 (API 30+) + insetsController.hide(WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars()); + insetsController.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE); + } + } else { + // 兼容旧版本 (API < 30) + View decorView = window.getDecorView(); + int flags = View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; + decorView.setSystemUiVisibility(flags); + } +} + +@Override +public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + if (hasFocus) { + hideSystemBars(); + } +} + +/** + * 检查并请求必要的权限 + */ +private void checkAndRequestPermissions() { + // 检查是否已经有所需的权限 + boolean allPermissionsGranted = true; + for (String permission : REQUIRED_PERMISSIONS) { + if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) { + allPermissionsGranted = false; + break; + } + } + + // 如果没有所有权限,则请求它们 + if (!allPermissionsGranted) { + ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, PERMISSION_REQUEST_CODE); + } +} + +/** + * 处理权限请求结果 + */ +@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, "部分权限被拒绝,可能影响应用功能"); + } + } +} + +} \ No newline at end of file diff --git a/app/src/main/java/com/ismart/ib86/common/utils/LogManager.java b/app/src/main/java/com/ismart/ib86/common/utils/LogManager.java new file mode 100644 index 0000000..9d5b71e --- /dev/null +++ b/app/src/main/java/com/ismart/ib86/common/utils/LogManager.java @@ -0,0 +1,122 @@ +package com.ismart.ib86.common.utils; + +import android.content.Context; +import android.util.Log; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Date; +import java.util.Locale; + +public final class LogManager implements Thread.UncaughtExceptionHandler { + private static LogManager instance = new LogManager(); + + private LogManager() { + } // 私有构造方法防止实例化 + + private static boolean isLogToFile = false; + private static String LOG_DIR = "logs"; + private static Context appContext; + private static final long MAX_LOG_SIZE = 10 * 1024 * 1024; // 10MB + private static final int MAX_LOG_FILES = 30; + + public static void init(Context context) { + Thread.setDefaultUncaughtExceptionHandler(instance); + appContext = context.getApplicationContext(); + LOG_DIR = appContext.getExternalFilesDir(null).getAbsolutePath() + "/logs"; + } + + private static final SimpleDateFormat FILE_DATE_FORMAT = new SimpleDateFormat("yyyyMMdd", Locale.getDefault()); + private static final SimpleDateFormat LOG_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", + Locale.getDefault()); + + public static void setLogToFile(boolean enable) { + isLogToFile = enable; + } + + private static void writeToFile(String tag, String level, String msg) { + if (!isLogToFile) + return; + + if (appContext == null) { + Log.e("LogManager", "Context not initialized, call init() first"); + return; + } + + try { + File logDir = new File(LOG_DIR); + String fullPath = logDir.getAbsolutePath(); + Log.d("LogPath", "日志存储路径: " + fullPath); + if (!logDir.exists()) { + logDir.mkdirs(); + } + + // 清理旧日志文件 + File[] logFiles = logDir.listFiles(); + if (logFiles != null && logFiles.length > MAX_LOG_FILES) { + Arrays.sort(logFiles, (f1, f2) -> Long.compare(f1.lastModified(), f2.lastModified())); + for (int i = 0; i < logFiles.length - MAX_LOG_FILES; i++) { + logFiles[i].delete(); + } + } + + String fileName = "log_" + FILE_DATE_FORMAT.format(new Date()) + ".txt"; + File logFile = new File(logDir, fileName); + + // 检查当前日志文件大小 + if (logFile.exists() && logFile.length() > MAX_LOG_SIZE) { + logFile.delete(); + } + + String threadInfo = "[" + Thread.currentThread().getId() + "/" + Thread.currentThread().getName() + "]"; + String deviceInfo = "[" + android.os.Build.MODEL + "/" + android.os.Build.VERSION.RELEASE + "]"; + String logContent = LOG_DATE_FORMAT.format(new Date()) + " " + level + "/" + tag + ": " + threadInfo + " " + + deviceInfo + " " + msg + "\n"; + + FileOutputStream fos = new FileOutputStream(logFile, true); + fos.write(logContent.getBytes()); + fos.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static int v(String tag, String msg) { + writeToFile(tag, "VERBOSE", msg); + return Log.v(tag, msg); + } + + public static int d(String tag, String msg) { + writeToFile(tag, "DEBUG", msg); + return Log.d(tag, msg); + } + + public static int i(String tag, String msg) { + writeToFile(tag, "INFO", msg); + return Log.i(tag, msg); + } + + public static int w(String tag, String msg) { + writeToFile(tag, "WARN", msg); + return Log.w(tag, msg); + } + + @Override + public void uncaughtException(Thread thread, Throwable ex) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + ex.printStackTrace(pw); + String stackTrace = sw.toString(); + e("CRASH", "Uncaught exception in thread " + thread.getName() + ": " + ex.getMessage() + "\n" + stackTrace); + } + + public static int e(String tag, String msg) { + writeToFile(tag, "ERROR", msg); + return Log.e(tag, msg); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ismart/ib86/feature/gpio/GpioManager.java b/app/src/main/java/com/ismart/ib86/feature/gpio/GpioManager.java new file mode 100644 index 0000000..2db1f87 --- /dev/null +++ b/app/src/main/java/com/ismart/ib86/feature/gpio/GpioManager.java @@ -0,0 +1,368 @@ +package com.ismart.ib86.feature.gpio; + +import com.ismart.ib86.common.utils.LogManager; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * GPIO管理类,提供GPIO的基本操作 + * 纯Java实现,直接操作/sys/class/gpio/目录下的文件 + */ +public class GpioManager { + private static final String TAG = "GpioManager"; + private static volatile GpioManager instance; + + // GPIO方向常量 + public static final int DIRECTION_IN = 0; + public static final int DIRECTION_OUT = 1; + + // GPIO值常量 + public static final int VALUE_LOW = 0; + public static final int VALUE_HIGH = 1; + + // GPIO路径常量 + private static final String GPIO_PATH = "/sys/class/gpio/"; + private static final String GPIO_EXPORT_PATH = GPIO_PATH + "export"; + private static final String GPIO_UNEXPORT_PATH = GPIO_PATH + "unexport"; + + // 文件缓存,避免频繁打开关闭文件 + private final Map fileCache = new HashMap<>(); + + // 最后的错误信息 + private String lastErrorMsg = ""; + + private GpioManager() { + init(); + } + + public static GpioManager getInstance() { + if (instance == null) { + synchronized (GpioManager.class) { + if (instance == null) { + instance = new GpioManager(); + } + } + } + return instance; + } + + /** + * 初始化GPIO管理器 + * @return 初始化是否成功 + */ + private boolean init() { + LogManager.d(TAG, "初始化GPIO管理器"); + + // 检查GPIO目录是否存在 + File gpioDir = new File(GPIO_PATH); + if (!gpioDir.exists() || !gpioDir.isDirectory()) { + setLastError("GPIO sysfs接口不可用: 目录不存在"); + return false; + } + // 检查export和unexport文件的访问权限 + File exportFile = new File(GPIO_EXPORT_PATH); + File unexportFile = new File(GPIO_UNEXPORT_PATH); + + if (!exportFile.exists() || !exportFile.canWrite()) { + setLastError("无法访问文件 " + GPIO_EXPORT_PATH + ": 文件不存在或无写入权限"); + return false; + } + + if (!unexportFile.exists() || !unexportFile.canWrite()) { + setLastError("无法访问文件 " + GPIO_UNEXPORT_PATH + ": 文件不存在或无写入权限"); + return false; + } + + LogManager.d(TAG, "GPIO管理器初始化成功"); + return true; + } + + /** + * 导出GPIO引脚 + * @param pin GPIO引脚号 + * @return 操作是否成功 + */ + public boolean exportGpio(int pin) { + //LogManager.d(TAG, "导出GPIO: " + pin); + + // 检查GPIO是否已经导出 + String gpioPath = GPIO_PATH + "gpio" + pin; + File gpioDir = new File(gpioPath); + + if (gpioDir.exists() && gpioDir.isDirectory()) { + //LogManager.d(TAG, "GPIO " + pin + " 已经导出"); + return true; + } + + // 导出GPIO + if (!writeToFile(GPIO_EXPORT_PATH, String.valueOf(pin))) { + return false; + } + + // 等待GPIO目录创建完成 + for (int i = 0; i < 10; i++) { + if (gpioDir.exists() && gpioDir.isDirectory()) { + //LogManager.d(TAG, "GPIO " + pin + " 导出成功"); + return true; + } + + try { + TimeUnit.MILLISECONDS.sleep(100); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + LogManager.e(TAG, "等待GPIO导出被中断: " + e.getMessage()); + } + } + + setLastError("GPIO " + pin + " 导出超时"); + return false; + } + + /** + * 取消导出GPIO引脚 + * @param pin GPIO引脚号 + * @return 操作是否成功 + */ + public boolean unexportGpio(int pin) { + LogManager.d(TAG, "取消导出GPIO: " + pin); + + // 检查GPIO是否已经导出 + String gpioPath = GPIO_PATH + "gpio" + pin; + File gpioDir = new File(gpioPath); + + if (!gpioDir.exists() || !gpioDir.isDirectory()) { + LogManager.d(TAG, "GPIO " + pin + " 未导出"); + return true; + } + + // 从缓存中移除相关文件 + String directionPath = gpioPath + "/direction"; + String valuePath = gpioPath + "/value"; + + synchronized (fileCache) { + fileCache.remove(directionPath); + fileCache.remove(valuePath); + } + + // 取消导出GPIO + if (!writeToFile(GPIO_UNEXPORT_PATH, String.valueOf(pin))) { + return false; + } + + // 等待GPIO目录删除完成 + for (int i = 0; i < 10; i++) { + if (!gpioDir.exists()) { + LogManager.d(TAG, "GPIO " + pin + " 取消导出成功"); + return true; + } + + try { + TimeUnit.MILLISECONDS.sleep(100); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + LogManager.e(TAG, "等待GPIO取消导出被中断: " + e.getMessage()); + } + } + + setLastError("GPIO " + pin + " 取消导出超时"); + return false; + } + + /** + * 设置GPIO方向 + * @param pin GPIO引脚号 + * @param direction 方向(DIRECTION_IN或DIRECTION_OUT) + * @return 操作是否成功 + */ + public boolean setDirection(int pin, int direction) { + if (direction != DIRECTION_IN && direction != DIRECTION_OUT) { + LogManager.e(TAG, "无效的方向值: " + direction); + return false; + } + + //LogManager.d(TAG, "设置GPIO " + pin + " 方向为: " + (direction == DIRECTION_IN ? "IN" : "OUT")); + + // 检查GPIO是否已导出 + String gpioPath = GPIO_PATH + "gpio" + pin; + File gpioDir = new File(gpioPath); + + if (!gpioDir.exists() || !gpioDir.isDirectory()) { + setLastError("GPIO " + pin + " 未导出"); + return false; + } + + // 设置方向 + String directionPath = gpioPath + "/direction"; + String dirStr = (direction == DIRECTION_IN) ? "in" : "out"; + + if (!writeToFile(directionPath, dirStr)) { + return false; + } + + //LogManager.d(TAG, "GPIO " + pin + " 方向设置为 " + dirStr + " 成功"); + return true; + } + + /** + * 设置GPIO值 + * @param pin GPIO引脚号 + * @param value 值(VALUE_LOW或VALUE_HIGH) + * @return 操作是否成功 + */ + public boolean setValue(int pin, int value) { + if (value != VALUE_LOW && value != VALUE_HIGH) { + LogManager.e(TAG, "无效的值: " + value); + return false; + } + + //LogManager.d(TAG, "设置GPIO " + pin + " 值为: " + value); + + // 检查GPIO是否已导出 + String gpioPath = GPIO_PATH + "gpio" + pin; + File gpioDir = new File(gpioPath); + + if (!gpioDir.exists() || !gpioDir.isDirectory()) { + setLastError("GPIO " + pin + " 未导出"); + return false; + } + + // 设置值 + String valuePath = gpioPath + "/value"; + String valueStr = String.valueOf(value); + + if (!writeToFile(valuePath, valueStr)) { + return false; + } + + //LogManager.d(TAG, "GPIO " + pin + " 值设置为 " + valueStr + " 成功"); + return true; + } + + /** + * 获取GPIO值 + * @param pin GPIO引脚号 + * @return GPIO值,-1表示获取失败 + */ + public int getValue(int pin) { + //LogManager.d(TAG, "获取GPIO " + pin + " 值"); + + // 检查GPIO是否已导出 + String gpioPath = GPIO_PATH + "gpio" + pin; + File gpioDir = new File(gpioPath); + + if (!gpioDir.exists() || !gpioDir.isDirectory()) { + setLastError("GPIO " + pin + " 未导出"); + return -1; + } + + // 读取值 + String valuePath = gpioPath + "/value"; + String value = readFromFile(valuePath); + + if (value == null || value.isEmpty()) { + return -1; + } + + // 解析值 + try { + int result = Integer.parseInt(value.trim()); + //LogManager.d(TAG, "GPIO " + pin + " 值读取为 " + result); + return result; + } catch (NumberFormatException e) { + setLastError("无法解析GPIO " + pin + " 的值: " + e.getMessage()); + return -1; + } + } + + /** + * 释放资源 + */ + public void release() { + LogManager.d(TAG, "释放GPIO资源"); + + // 清空文件缓存 + synchronized (fileCache) { + fileCache.clear(); + } + + instance = null; + } + + /** + * 获取最后的错误信息 + * @return 错误信息 + */ + public String getLastError() { + return lastErrorMsg; + } + + /** + * 设置最后的错误信息 + * @param message 错误信息 + */ + private void setLastError(String message) { + lastErrorMsg = message; + LogManager.e(TAG, message); + } + + /** + * 写入文件 + * @param path 文件路径 + * @param content 内容 + * @return 是否成功 + */ + private boolean writeToFile(String path, String content) { + BufferedWriter writer = null; + + try { + writer = new BufferedWriter(new FileWriter(path)); + writer.write(content); + writer.flush(); + return true; + } catch (IOException e) { + setLastError("写入文件 " + path + " 失败: " + e.getMessage()); + return false; + } finally { + if (writer != null) { + try { + writer.close(); + } catch (IOException e) { + LogManager.e(TAG, "关闭文件写入流失败: " + e.getMessage()); + } + } + } + } + + /** + * 从文件读取 + * @param path 文件路径 + * @return 文件内容,失败返回null + */ + private String readFromFile(String path) { + BufferedReader reader = null; + + try { + reader = new BufferedReader(new FileReader(path)); + return reader.readLine(); + } catch (IOException e) { + setLastError("读取文件 " + path + " 失败: " + e.getMessage()); + return null; + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + LogManager.e(TAG, "关闭文件读取流失败: " + e.getMessage()); + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ismart/ib86/feature/gpio/GpioTest.java b/app/src/main/java/com/ismart/ib86/feature/gpio/GpioTest.java new file mode 100644 index 0000000..b25e993 --- /dev/null +++ b/app/src/main/java/com/ismart/ib86/feature/gpio/GpioTest.java @@ -0,0 +1,132 @@ +package com.ismart.ib86.feature.gpio; + +import com.ismart.ib86.common.utils.LogManager; + +/** + * GPIO测试类,用于测试GPIO功能 + */ +public class GpioTest { + private static final String TAG = "GpioTest"; + + // 测试用的GPIO引脚号,根据实际硬件调整 + private static final int TEST_GPIO_PIN = 26; // 示例引脚号,需要根据实际硬件调整 + + private GpioManager gpioManager; + + public GpioTest() { + gpioManager = GpioManager.getInstance(); + LogManager.d(TAG, "GpioTest初始化完成"); + } + + /** + * 开始GPIO测试 + */ + public void startTest() { + LogManager.d(TAG, "开始GPIO测试..."); + + try { + // 测试1: 导出GPIO引脚 + testExportGpio(); + + // 测试2: 设置GPIO方向 + testSetDirection(); + + // 测试3: 设置和获取GPIO值 + testSetAndGetValue(); + + // 测试4: 取消导出GPIO引脚 + testUnexportGpio(); + + LogManager.d(TAG, "GPIO测试完成"); + } catch (Exception e) { + LogManager.e(TAG, "GPIO测试异常: " + e.getMessage()); + } + } + + /** + * 测试导出GPIO引脚 + */ + private void testExportGpio() { + LogManager.d(TAG, "测试导出GPIO引脚 " + TEST_GPIO_PIN); + boolean result = gpioManager.exportGpio(TEST_GPIO_PIN); + if (result) { + LogManager.d(TAG, "GPIO引脚 " + TEST_GPIO_PIN + " 导出成功"); + } else { + LogManager.e(TAG, "GPIO引脚 " + TEST_GPIO_PIN + " 导出失败"); + } + } + + /** + * 测试设置GPIO方向 + */ + private void testSetDirection() { + LogManager.d(TAG, "测试设置GPIO方向为输出"); + boolean result = gpioManager.setDirection(TEST_GPIO_PIN, GpioManager.DIRECTION_OUT); + if (result) { + LogManager.d(TAG, "GPIO引脚 " + TEST_GPIO_PIN + " 方向设置为输出成功"); + } else { + LogManager.e(TAG, "GPIO引脚 " + TEST_GPIO_PIN + " 方向设置为输出失败"); + } + } + + /** + * 测试设置和获取GPIO值 + */ + private void testSetAndGetValue() { + // 设置为高电平 + LogManager.d(TAG, "测试设置GPIO值为高电平"); + boolean setHighResult = gpioManager.setValue(TEST_GPIO_PIN, GpioManager.VALUE_HIGH); + if (setHighResult) { + LogManager.d(TAG, "GPIO引脚 " + TEST_GPIO_PIN + " 设置为高电平成功"); + } else { + LogManager.e(TAG, "GPIO引脚 " + TEST_GPIO_PIN + " 设置为高电平失败"); + } + + // 读取值 + int highValue = gpioManager.getValue(TEST_GPIO_PIN); + LogManager.d(TAG, "GPIO引脚 " + TEST_GPIO_PIN + " 当前值: " + highValue); + + // 等待一段时间 + try { + Thread.sleep(500); + } catch (InterruptedException e) { + LogManager.e(TAG, "线程睡眠被中断: " + e.getMessage()); + } + + // 设置为低电平 + LogManager.d(TAG, "测试设置GPIO值为低电平"); + boolean setLowResult = gpioManager.setValue(TEST_GPIO_PIN, GpioManager.VALUE_LOW); + if (setLowResult) { + LogManager.d(TAG, "GPIO引脚 " + TEST_GPIO_PIN + " 设置为低电平成功"); + } else { + LogManager.e(TAG, "GPIO引脚 " + TEST_GPIO_PIN + " 设置为低电平失败"); + } + + // 读取值 + int lowValue = gpioManager.getValue(TEST_GPIO_PIN); + LogManager.d(TAG, "GPIO引脚 " + TEST_GPIO_PIN + " 当前值: " + lowValue); + } + + /** + * 测试取消导出GPIO引脚 + */ + private void testUnexportGpio() { + LogManager.d(TAG, "测试取消导出GPIO引脚 " + TEST_GPIO_PIN); + boolean result = gpioManager.unexportGpio(TEST_GPIO_PIN); + if (result) { + LogManager.d(TAG, "GPIO引脚 " + TEST_GPIO_PIN + " 取消导出成功"); + } else { + LogManager.e(TAG, "GPIO引脚 " + TEST_GPIO_PIN + " 取消导出失败"); + } + } + + /** + * 释放资源 + */ + public void release() { + LogManager.d(TAG, "释放GPIO测试资源"); + if (gpioManager != null) { + gpioManager.release(); + } + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..a9ba430 --- /dev/null +++ b/app/src/main/java/com/ismart/ib86/feature/serial/SerialPort/ASR5515DeviceManager.java @@ -0,0 +1,422 @@ +package com.ismart.ib86.feature.serial.SerialPort; + +import android.content.Context; +import android.os.Handler; +import android.os.Looper; + +import com.ismart.ib86.common.utils.LogManager; +import com.ismart.ib86.feature.gpio.GpioManager; + +/** + * ASR-5515设备管理器 + * 用于管理与ASR-5515设备的通信 + */ +public class ASR5515DeviceManager { + private static final String TAG = "ASR5515DeviceManager"; + private static final int GPIO_BT_WAKE = 33; // 蓝牙唤醒GPIO + + private final SerialPortHelper serialPortHelper; + private final Handler mainHandler; + private final GpioManager gpioManager; + // 细分回调实例 + private DeviceCheckListener deviceCheckListener; + private DeviceControlListener deviceControlListener; + private LogControlListener logControlListener; + private BtVersionListener btVersionListener; + private BtUpgradeListener btUpgradeListener; + private HostStatusListener hostStatusListener; + private BootBinCheckListener bootBinCheckListener; + private CollectFreqSetListener collectFreqSetListener; + private HrBpBoAutoMeasureListener hrBpBoAutoMeasureListener; + private HrBpBoManualMeasureListener hrBpBoManualMeasureListener; + private DynamicMeasureListener dynamicMeasureListener; + private WearDetectionListener wearDetectionListener; + + // 细分回调接口 + public interface DeviceCheckListener { + void onDeviceCheckResponse(ASR5515Protocol.DeviceInfo deviceInfo); + } + + public interface DeviceControlListener { + void onDeviceRestartResponse(boolean success); + } + + public interface LogControlListener { + void onLogControlResponse(boolean success); + } + + public interface BtVersionListener { + void onBtVersionResponse(String version); + } + + public interface BtUpgradeListener { + void onBtUpgradeResponse(ASR5515Protocol.BtUpgradeResponse response); + } + + public interface HostStatusListener { + void onHostStatusResponse(ASR5515Protocol.HostStatusResponse response); + } + + public interface BootBinCheckListener { + void onBootBinCheckResponse(ASR5515Protocol.BootBinCheckResponse response); + } + + public interface CollectFreqSetListener { + void onCollectFreqSetResponse(ASR5515Protocol.CollectFreqSetResponse response); + } + + public interface HrBpBoAutoMeasureListener { + void onHrBpBoAutoMeasureResponse(ASR5515Protocol.HrBpBoAutoMeasureResponse response); + } + + public interface HrBpBoManualMeasureListener { + void onHrBpBoManualMeasureResponse(ASR5515Protocol.HrBpBoManualMeasureResponse response); + } + + public interface DynamicMeasureListener { + void onDynamicMeasureResponse(ASR5515Protocol.DynamicMeasureResponse response); + } + + public interface WearDetectionListener { + void onWearDetectionResponse(ASR5515Protocol.WearDetectionResponse response); + } + + /** + * 构造函数 + * @param context 上下文 + * @param devicePath 设备路径 + * @param baudRate 波特率 + */ + 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); + } + + // 设置设备状态回调 + serialPortHelper.setDeviceCheckCallback(new SerialPortHelper.DeviceCheckCallback() { + @Override + public void onDeviceCheckResponse(final ASR5515Protocol.DeviceInfo deviceInfo) { + if (deviceCheckListener != null) { + mainHandler.post(() -> deviceCheckListener.onDeviceCheckResponse(deviceInfo)); + } + } + }); + + serialPortHelper.setDeviceControlCallback(new SerialPortHelper.DeviceControlCallback() { + @Override + public void onDeviceRestartResponse(final boolean success) { + if (deviceControlListener != null) { + mainHandler.post(() -> deviceControlListener.onDeviceRestartResponse(success)); + } + } + }); + + serialPortHelper.setLogControlCallback(new SerialPortHelper.LogControlCallback() { + @Override + public void onLogControlResponse(final boolean success) { + if (logControlListener != null) { + mainHandler.post(() -> logControlListener.onLogControlResponse(success)); + } + } + }); + + serialPortHelper.setBtVersionCallback(new SerialPortHelper.BtVersionCallback() { + @Override + public void onBtVersionResponse(final String version) { + if (btVersionListener != null) { + mainHandler.post(() -> btVersionListener.onBtVersionResponse(version)); + } + } + }); + + serialPortHelper.setBtUpgradeCallback(new SerialPortHelper.BtUpgradeCallback() { + @Override + public void onBtUpgradeResponse(final ASR5515Protocol.BtUpgradeResponse response) { + if (btUpgradeListener != null) { + mainHandler.post(() -> btUpgradeListener.onBtUpgradeResponse(response)); + } + } + }); + + serialPortHelper.setHostStatusCallback(new SerialPortHelper.HostStatusCallback() { + @Override + public void onHostStatusResponse(final ASR5515Protocol.HostStatusResponse response) { + if (hostStatusListener != null) { + mainHandler.post(() -> hostStatusListener.onHostStatusResponse(response)); + } + } + }); + + serialPortHelper.setBootBinCheckCallback(new SerialPortHelper.BootBinCheckCallback() { + @Override + public void onBootBinCheckResponse(final ASR5515Protocol.BootBinCheckResponse response) { + if (bootBinCheckListener != null) { + mainHandler.post(() -> bootBinCheckListener.onBootBinCheckResponse(response)); + } + } + }); + + serialPortHelper.setCollectFreqSetCallback(new SerialPortHelper.CollectFreqSetCallback() { + @Override + public void onCollectFreqSetResponse(final ASR5515Protocol.CollectFreqSetResponse response) { + if (collectFreqSetListener != null) { + mainHandler.post(() -> collectFreqSetListener.onCollectFreqSetResponse(response)); + } + } + }); + + serialPortHelper.setHrBpBoAutoMeasureCallback(new SerialPortHelper.HrBpBoAutoMeasureCallback() { + @Override + public void onHrBpBoAutoMeasureResponse(ASR5515Protocol.HrBpBoAutoMeasureResponse response) { + if (hrBpBoAutoMeasureListener != null) { + mainHandler.post(() -> hrBpBoAutoMeasureListener.onHrBpBoAutoMeasureResponse(response)); + } + } + }); + + serialPortHelper.setHrBpBoManualMeasureCallback(new SerialPortHelper.HrBpBoManualMeasureCallback() { + @Override + public void onHrBpBoManualMeasureResponse(ASR5515Protocol.HrBpBoManualMeasureResponse response) { + if (hrBpBoManualMeasureListener != null) { + mainHandler.post(() -> hrBpBoManualMeasureListener.onHrBpBoManualMeasureResponse(response)); + } + } + }); + + serialPortHelper.setDynamicMeasureCallback(new SerialPortHelper.DynamicMeasureCallback() { + @Override + public void onDynamicMeasureResponse(ASR5515Protocol.DynamicMeasureResponse response) { + if (dynamicMeasureListener != null) { + mainHandler.post(() -> dynamicMeasureListener.onDynamicMeasureResponse(response)); + } + } + }); + + serialPortHelper.setWearDetectionCallback(new SerialPortHelper.WearDetectionCallback() { + @Override + public void onWearDetectionResponse(ASR5515Protocol.WearDetectionResponse response) { + if (wearDetectionListener != null) { + mainHandler.post(() -> wearDetectionListener.onWearDetectionResponse(response)); + } + } + }); + } + + /** + * 设置设备检查监听器 + */ + public void setDeviceCheckListener(DeviceCheckListener listener) { + this.deviceCheckListener = listener; + } + + /** + * 设置设备控制监听器 + */ + public void setDeviceControlListener(DeviceControlListener listener) { + this.deviceControlListener = listener; + } + + public void setLogControlListener(LogControlListener listener) { + this.logControlListener = listener; + } + + /** + * 设置蓝牙版本监听器 + */ + public void setBtVersionListener(BtVersionListener listener) { + this.btVersionListener = listener; + } + + /** + * 设置蓝牙升级监听器 + */ + public void setBtUpgradeListener(BtUpgradeListener listener) { + this.btUpgradeListener = listener; + } + + /** + * 设置主机状态监听器 + */ + public void setHostStatusListener(HostStatusListener listener) { + this.hostStatusListener = listener; + } + + /** + * 设置心率血压血氧自动测量监听器 + */ + public void setHrBpBoAutoMeasureListener(HrBpBoAutoMeasureListener listener) { + this.hrBpBoAutoMeasureListener = listener; + } + + /** + * 设置心率血压血氧手动测量监听器 + */ + public void setHrBpBoManualMeasureListener(HrBpBoManualMeasureListener listener) { + this.hrBpBoManualMeasureListener = listener; + } + + /** + * 设置动态测量监听器 + */ + public void setDynamicMeasureListener(DynamicMeasureListener listener) { + this.dynamicMeasureListener = listener; + } + + /** + * 设置佩戴检测监听器 + */ + public void setWearDetectionListener(WearDetectionListener listener) { + this.wearDetectionListener = listener; + } + + /** + * 设置Boot Bin检查监听器 + */ + public void setBootBinCheckListener(BootBinCheckListener listener) { + this.bootBinCheckListener = listener; + } + + /** + * 设置采集频率设置监听器 + */ + public void setCollectFreqSetListener(CollectFreqSetListener listener) { + this.collectFreqSetListener = listener; + } + + /** + * 检查设备状态 + */ + public void checkDevice() { + wakeupDevice(); + serialPortHelper.checkDevice(); + } + + /** + * 重启设备 + */ + public void restartDevice() { + wakeupDevice(); + serialPortHelper.restartDevice(); + } + + /** + * 控制日志输出 + * @param enable 是否启用 + */ + public void setLogControl(boolean enable) { + wakeupDevice(); + serialPortHelper.setLogControl(enable); + } + + /** + * 查询蓝牙版本 + */ + public void queryBtVersion() { + wakeupDevice(); + serialPortHelper.queryBtVersion(); + } + + /** + * 发送蓝牙升级请求 + */ + public void upgradeBt() { + wakeupDevice(); + serialPortHelper.upgradeBt(); + } + + /** + * 发送主机状态同步请求 + * @param screenOn 屏幕状态(true:亮屏,false:灭屏) + */ + public void syncHostStatus(boolean screenOn) { + wakeupDevice(); + serialPortHelper.syncHostStatus(screenOn); + } + + /** + * 关闭设备 + */ + public void close() { + serialPortHelper.close(); + } + + /** + * 发送boot bin检查请求 + */ + public void checkBootBin() { + wakeupDevice(); + serialPortHelper.checkBootBin(); + } + + /** + * 设置采集频率 + * @param len 长度 + * @param freq 频率值 + */ + public void setCollectFreq(byte len, byte freq) { + wakeupDevice(); + serialPortHelper.setCollectFreq(len, freq); + } + + /** + * 发送HrBpBo自动测量请求 + */ + public void startHrBpBoAutoMeasure() { + wakeupDevice(); + serialPortHelper.startHrBpBoAutoMeasure(); + } + + /** + * 发送HrBpBo手动测量请求 + */ + public void startHrBpBoManualMeasure() { + wakeupDevice(); + serialPortHelper.startHrBpBoManualMeasure(); + } + + /** + * 发送动态测量请求 + */ + public void startDynamicMeasure() { + wakeupDevice(); + serialPortHelper.startDynamicMeasure(); + } + + /** + * 发送佩戴检测请求 + */ + public void sendWearDetectionRequest() { + wakeupDevice(); + serialPortHelper.sendWearDetectionRequest(); + } + + /** + * 唤醒设备 + * 通过GPIO33唤醒蓝牙,ASR休眠时会自动发送'w'字符 + */ + 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()); + } + } +} \ 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 new file mode 100644 index 0000000..de25c28 --- /dev/null +++ b/app/src/main/java/com/ismart/ib86/feature/serial/SerialPort/ASR5515Protocol.java @@ -0,0 +1,648 @@ +package com.ismart.ib86.feature.serial.SerialPort; + +import static com.ismart.ib86.feature.serial.SerialPort.SerialPortHelper.bytesToHexString; + +import android.os.Build; +import android.util.Log; + +import com.ismart.ib86.common.utils.LogManager; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.HexFormat; + +public class ASR5515Protocol { + private static final String TAG = "ASR5515Protocol"; + // 设备检查回调接口 + public interface DeviceCheckCallback { + void onDeviceCheckResponse(DeviceInfo deviceInfo); + } + + // 设备控制回调接口 + public interface DeviceControlCallback { + void onDeviceRestartResponse(boolean success); + void onLogControlResponse(boolean success); + } + + // 蓝牙版本回调接口 + public interface BtVersionCallback { + void onBtVersionResponse(String version); + } + + // 蓝牙升级回调接口 + public interface BtUpgradeCallback { + void onBtUpgradeResponse(BtUpgradeResponse response); + } + + // 主机状态回调接口 + public interface HostStatusCallback { + void onHostStatusResponse(HostStatusResponse response); + } + + // Boot Bin检查回调接口 + public interface BootBinCheckCallback { + void onBootBinCheckResponse(BootBinCheckResponse response); + } + + // 采集频率设置回调接口 + public interface CollectFreqSetCallback { + void onCollectFreqSetResponse(CollectFreqSetResponse response); + } + + // HrBpBo自动测量回调接口 + public interface HrBpBoAutoMeasureCallback { + void onHrBpBoAutoMeasureResponse(HrBpBoAutoMeasureResponse response); + } + + // HrBpBo手动测量回调接口 + public interface HrBpBoManualMeasureCallback { + void onHrBpBoManualMeasureResponse(HrBpBoManualMeasureResponse response); + } + + // 动态测量回调接口 + public interface DynamicMeasureCallback { + void onDynamicMeasureResponse(DynamicMeasureResponse response); + } + + // 佩戴检测回调接口 + public interface WearDetectionCallback { + void onWearDetectionResponse(WearDetectionResponse response); + } + private static final byte FRAME_START = (byte) '['; + private static final byte FRAME_END = (byte) ']'; + + public static class DeviceInfo { + public byte sn; // 序列号 + public byte len; // 长度 + public byte heartRate; // 心率 + public byte heartRateId; // 心率ID + public byte stepCount; // 计步 + public byte stepCountId; // 计步ID + public String deviceId; // 设备ID + public String hardwareVersion; // 硬件版本 + public String softwareVersion; // 软件版本 + public String status; // 设备状态 + + public DeviceInfo(byte sn, byte len, byte heartRate, byte heartRateId, byte stepCount, byte stepCountId, + String deviceId, String hardwareVersion, String softwareVersion, String status) { + this.sn = sn; + this.len = len; + this.heartRate = heartRate; + this.heartRateId = heartRateId; + this.stepCount = stepCount; + this.stepCountId = stepCountId; + this.deviceId = deviceId; + this.hardwareVersion = hardwareVersion; + this.softwareVersion = softwareVersion; + this.status = status; + } + + @Override + public String toString() { + return String.format("设备信息:\n设备ID: %s\n硬件版本: %s\n软件版本: %s\n状态: %s", + deviceId, hardwareVersion, softwareVersion, status); + } + } + + // 第一个parseDeviceCheckResponse方法被删除,保留文件末尾的更详细实现 + + public static class RebootResponse { + public byte sn; + public byte status; + + public RebootResponse(byte sn, byte status) { + this.sn = sn; + this.status = status; + } + } + + public static RebootResponse parseRebootResponse(Frame frame) { + if (frame == null || frame.command != Commands.CMD_DEVICE_REBOOT_RESP || frame.data.length < 2) { + return null; + } + return new RebootResponse(frame.data[0], frame.data[1]); + } + + public static class TimeSyncResponse { + public byte sn; + public byte status; + + public TimeSyncResponse(byte sn, byte status) { + this.sn = sn; + this.status = status; + } + } + + public static TimeSyncResponse parseTimeSyncResponse(Frame frame) { + if (frame == null || frame.command != Commands.CMD_TIME_SYNC_RESP || frame.data.length < 2) { + return null; + } + return new TimeSyncResponse(frame.data[0], frame.data[1]); + } + + public static class LogControlResponse { + public byte sn; + public byte status; + + public LogControlResponse(byte sn, byte status) { + this.sn = sn; + this.status = status; + } + } + + public static LogControlResponse parseLogControlResponse(Frame frame) { + if (frame == null || frame.command != Commands.CMD_LOG_CONTROL_RESP || frame.data.length < 2) { + return null; + } + return new LogControlResponse(frame.data[0], frame.data[1]); + } + + // 创建设备重启请求帧 + public static byte[] createRebootRequest(byte sn) { + return createFrame(Commands.CMD_DEVICE_REBOOT, new byte[]{sn, 0}); + } + + // 创建时间同步请求帧 + public static byte[] createTimeSyncRequest(byte sn, byte len, int timestamp) { + byte[] data = new byte[6]; + data[0] = sn; + data[1] = len; + // 将timestamp转换为4字节数组 + data[2] = (byte) (timestamp >> 24); + data[3] = (byte) (timestamp >> 16); + data[4] = (byte) (timestamp >> 8); + data[5] = (byte) timestamp; + return createFrame(Commands.CMD_TIME_SYNC, data); + } + + // 创建日志打印控制请求帧 + public static byte[] createLogControlRequest(byte sn, byte len, byte enable) { + return createFrame(Commands.CMD_LOG_CONTROL, new byte[]{sn, len, enable}); + } + + public static class Commands { + public static final short CMD_CHECK_DEVICE = 0x0065; // 设备检查请求 + public static final short CMD_CHECK_DEVICE_RESP = 0x0066; // 设备检查响应 + public static final short CMD_DEVICE_REBOOT = 0x0067; // 设备重启请求 + public static final short CMD_DEVICE_REBOOT_RESP = 0x0068;// 设备重启响应 + public static final short CMD_TIME_SYNC = 0x0069; // 时间同步请求 + public static final short CMD_TIME_SYNC_RESP = 0x006A; // 时间同步响应 + public static final short CMD_RESTART_DEVICE = 0x0067; // 设备重启请求 + public static final short CMD_RESTART_DEVICE_RESP = 0x0068; // 设备重启响应 + public static final short CMD_LOG_CONTROL = 0x006B; // 日志打印控制请求 + public static final short CMD_LOG_CONTROL_RESP = 0x006C; // 日志打印控制响应 + public static final short CMD_BT_VERSION = 0x006D; // 蓝牙版本请求 + public static final short CMD_BT_VERSION_RESP = 0x006E; // 蓝牙版本响应 + public static final short CMD_BT_UPGRADE = 0x0071; // 本地、蓝牙升级请求 + public static final short CMD_BT_UPGRADE_RESP = 0x0078; // 本地、蓝牙升级响应 + 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] + public static final short CMD_COLLECT_FREQ_SET_RESP = 0x00CA; // 采集频率设置响应 [202 sn 0] + public static final short CMD_HRBPBO_AUTO_MEASURE = 0x00CB; // HrBpBo自动测量 [203 0 0] + public static final short CMD_HRBPBO_AUTO_MEASURE_RESP = 0x00CC; // HrBpBo自动测量响应 [204 sn 0] + public static final short CMD_HRBPBO_MANUAL_MEASURE = 0x00CD; // HrBpBo手动测量 [205 0 0] + public static final short CMD_HRBPBO_MANUAL_MEASURE_RESP = 0x00CE; // HrBpBo手动测量响应 [206 sn 0] + public static final short CMD_DYNAMIC_MEASURE = 0x00CF; // 动态测量 [207 0 0] + public static final short CMD_DYNAMIC_MEASURE_RESP = 0x00D0; // 动态测量响应 [208 sn 0] + public static final short CMD_WEAR_DETECTION = 0x0105; // 佩戴检测查询 [261 0 0] + public static final short CMD_WEAR_DETECTION_RESP = 0x0106; // 佩戴检测响应 [262 sn len 0/1] + } + + public static class Frame { + public short command; // 修改为short类型以支持2字节命令 + public byte[] data; + public byte[] remainingData; // 解析后剩余的数据 + + public Frame(short command, byte[] data) { + this.command = command; + this.data = data; + } + } + + public static byte[] createDeviceCheckRequest() { + // 发送格式:[101 0 0] + byte[] data = new byte[2]; // 两个0 + data[0] = 0; + data[1] = 0; + return createFrame(Commands.CMD_CHECK_DEVICE, data); + } + + public static byte[] createFrame(short command, byte[] data) { + 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); + + buffer.put(FRAME_START); // 添加帧起始符'[' + buffer.put((byte)(command & 0xFF)); // 命令字节低位 + buffer.put((byte)((command >> 8) & 0xFF)); // 命令字节高位 + buffer.put((byte)0x00); // sn + buffer.put((byte)0x00); //sn + buffer.put((byte)(data.length & 0xFF)); // len + buffer.put((byte)((data.length >> 8) & 0xFF)); //len + if(data.length > 0){ + buffer.put(data); // 数据部分 + } + buffer.put(FRAME_END); // 添加帧结束符']' + + return buffer.array(); + } + + public static Frame parseFrame(byte[] data) { + if (data == null || data.length < 8) { // 最小帧长度:head(1) + type(2) + sn(2) + len(2) + end(1) + return null; + } + + // 查找帧起始符'[' + int startIndex = -1; + for (int i = 0; i < data.length; i++) { + if (data[i] == FRAME_START) { + startIndex = i; + break; + } + } + + if (startIndex == -1 || startIndex + 7 >= data.length) { + return null; + } + + // 从起始符后开始查找帧结束符']' + int endIndex = -1; + for (int i = startIndex + 7; i < data.length; i++) { + if (data[i] == FRAME_END) { + endIndex = i; + break; + } + } + + if (endIndex == -1) { + return null; + } + + try { + // 提取type(2字节) + short type = (short)(((data[startIndex + 2] & 0xFF) << 8) | (data[startIndex + 1] & 0xFF)); + + // 提取sn(2字节) + short sn = (short)(((data[startIndex + 4] & 0xFF) << 8) | (data[startIndex + 3] & 0xFF)); + + // 提取len(2字节) + short len = (short)(((data[startIndex + 6] & 0xFF) << 8) | (data[startIndex + 5] & 0xFF)); + + // 提取数据部分(如果有的话) + int dataLength = endIndex - (startIndex + 7); // 7 = head(1) + type(2) + sn(2) + len(2) + //LogManager.d(TAG, "len: "+ len + " dataLength: " + dataLength + " end data" + data[endIndex]); + byte[] frameData; + if (dataLength > 0) { + frameData = new byte[dataLength]; + System.arraycopy(data, startIndex + 7, frameData, 0, dataLength); +// LogManager.d(TAG,"data :"+ bytesToHexString(frameData)); + } else { + frameData = new byte[0]; + } + + // 创建帧对象 + Frame frame = new Frame(type, frameData); + + // 如果还有剩余数据,保存到remainingData + if (endIndex + 1 < data.length) { + frame.remainingData = new byte[data.length - endIndex - 1]; + System.arraycopy(data, endIndex + 1, frame.remainingData, 0, frame.remainingData.length); + } + + return frame; + } catch (Exception e) { + LogManager.e(TAG, "Parse frame error: " + e.getMessage()); + return null; + } + } + + public static DeviceInfo parseDeviceCheckResponse(Frame frame) { + if (frame == null || frame.command != Commands.CMD_CHECK_DEVICE_RESP || frame.data.length < 7) { + return null; + } + + try { + ByteBuffer buffer = ByteBuffer.wrap(frame.data); + + // 解析响应数据:[sn len 心率 ID 计步 ID 设备ID] + byte sn = buffer.get(); // 序列号 + byte len = buffer.get(); // 长度 + byte heartRate = buffer.get(); // 心率 + byte heartRateId = buffer.get(); // 心率ID + byte stepCount = buffer.get(); // 计步 + byte stepCountId = buffer.get(); // 计步ID + byte rawDeviceId = buffer.get(); // 设备ID + + // 将deviceId转换为十六进制字符串 + String deviceId = String.format("%02X", rawDeviceId); + + // 硬件版本和软件版本可能不在帧数据中,设置默认值 + String hardwareVersion = "V1.0"; + String softwareVersion = "V1.0"; + String status = "正常"; // 默认状态 + + // 如果帧数据中包含更多信息,可以继续解析 + if (frame.data.length > 7) { + // 这里需要根据实际协议格式解析硬件版本、软件版本和状态 + byte statusByte = frame.data[7]; + status = parseStatus(statusByte); + } + + return new DeviceInfo(sn, len, heartRate, heartRateId, stepCount, stepCountId, deviceId, hardwareVersion, softwareVersion, status); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + private static String parseStatus(byte statusByte) { + switch(statusByte) { + case 0: + return "正常"; + case 1: + return "异常"; + case 2: + return "离线"; + default: + return "未知状态(" + statusByte + ")"; + } + } + + // 创建蓝牙版本请求帧 + public static byte[] createBtVersionRequest() { + return createFrame(Commands.CMD_BT_VERSION, new byte[]{}); + } + + // 创建设备重启请求帧 + public static byte[] createDeviceRestartRequest() { + return createFrame(Commands.CMD_RESTART_DEVICE, new byte[]{}); + } + + // 解析设备重启响应帧 + public static boolean parseDeviceRestartResponse(Frame frame) { + return frame != null && frame.command == Commands.CMD_RESTART_DEVICE_RESP; + } + + public static String parseBtVersionResponse(Frame frame) { + if (frame == null || frame.command != Commands.CMD_BT_VERSION_RESP || frame.data.length == 0) { + return null; + } + StringBuilder sb = new StringBuilder(); + for (byte b : frame.data) { + // 只保留可打印的 ASCII 字符(32-126) + if (b >= 32 && b <= 126) { + sb.append((char) b); + } + } + try { + return sb.toString(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + // 创建本地、蓝牙升级请求帧 + public static byte[] createBtUpgradeRequest() { + // 发送格式:[113 0 0] + byte[] data = new byte[2]; // 两个0 + data[0] = 0; + data[1] = 0; + return createFrame(Commands.CMD_BT_UPGRADE, data); + } + + // 解析本地、蓝牙升级响应帧 + public static class BtUpgradeResponse { + public byte sn; + public byte len; + public String data; + + public BtUpgradeResponse(byte sn, byte len, String data) { + this.sn = sn; + this.len = len; + this.data = data; + } + } + + public static BtUpgradeResponse parseBtUpgradeResponse(Frame frame) { + if (frame == null || frame.command != Commands.CMD_BT_UPGRADE_RESP || frame.data.length < 2) { + return null; + } + + try { + byte sn = frame.data[0]; + byte len = frame.data[1]; + + // 提取数据部分(如果有的话) + String data = ""; + if (frame.data.length > 2) { + byte[] dataBytes = new byte[frame.data.length - 2]; + System.arraycopy(frame.data, 2, dataBytes, 0, dataBytes.length); + data = new String(dataBytes, StandardCharsets.UTF_8); + } + + return new BtUpgradeResponse(sn, len, data); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + // 创建主机状态同步请求帧 + public static byte[] createHostStatusRequest(boolean screenOn) { + // 发送格式:[121 0 len 0/1],其中0/1表示屏幕状态(灭/亮) + byte[] data = new byte[3]; + data[0] = 0; // 保留字节 + data[1] = 1; + data[2] = (byte)(screenOn ? 1 : 0); // 1表示亮屏,0表示灭屏 + return createFrame(Commands.CMD_HOST_STATUS, data); + } + + // 创建boot bin检查请求帧 [125 0 0] + public static byte[] createBootBinCheckRequest() { + byte[] data = new byte[2]; + data[0] = 0; + data[1] = 0; + return createFrame(Commands.CMD_BOOT_BIN_CHECK, data); + } + + // 创建采集频率设置请求帧 [201 0 len 32] + public static byte[] createCollectFreqSetRequest(byte len, byte freq) { + byte[] data = new byte[3]; + data[0] = 0; // 保留字节 + data[1] = len; + data[2] = freq; + return createFrame(Commands.CMD_COLLECT_FREQ_SET, data); + } + + // 创建HrBpBo自动测量请求帧 [203 0 0] + public static byte[] createHrBpBoAutoMeasureRequest() { + byte[] data = new byte[2]; + data[0] = 0; + data[1] = 0; + return createFrame(Commands.CMD_HRBPBO_AUTO_MEASURE, data); + } + + // 创建HrBpBo手动测量请求帧 [205 0 0] + public static byte[] createHrBpBoManualMeasureRequest() { + byte[] data = new byte[2]; + data[0] = 0; + data[1] = 0; + return createFrame(Commands.CMD_HRBPBO_MANUAL_MEASURE, data); + } + + // 创建动态测量请求帧 [207 0 0] + public static byte[] createDynamicMeasureRequest() { + byte[] data = new byte[2]; + data[0] = 0; + data[1] = 0; + return createFrame(Commands.CMD_DYNAMIC_MEASURE, data); + } + + // 解析主机状态同步响应帧 + public static class HostStatusResponse { + public byte sn; + public byte status; + + public HostStatusResponse(byte sn, byte status) { + this.sn = sn; + this.status = status; + } + } + + public static HostStatusResponse parseHostStatusResponse(Frame frame) { + if (frame == null || frame.data.length < 2) { + return null; + } + + return new HostStatusResponse(frame.data[0], frame.data[1]); + } + + // BootBinCheck响应类 + public static class BootBinCheckResponse { + public byte sn; + public byte len; + public byte status; + + public BootBinCheckResponse(byte sn, byte len, byte status) { + this.sn = sn; + this.len = len; + this.status = status; + } + } + + public static BootBinCheckResponse parseBootBinCheckResponse(Frame frame) { + if (frame == null || frame.command != Commands.CMD_BOOT_BIN_CHECK_RESP || frame.data.length < 3) { + return null; + } + return new BootBinCheckResponse(frame.data[0], frame.data[1], frame.data[2]); + } + + // CollectFreqSet响应类 + public static class CollectFreqSetResponse { + public byte sn; + public byte status; + + public CollectFreqSetResponse(byte sn, byte status) { + this.sn = sn; + this.status = status; + } + } + + public static CollectFreqSetResponse parseCollectFreqSetResponse(Frame frame) { + if (frame == null || frame.command != Commands.CMD_COLLECT_FREQ_SET_RESP || frame.data.length < 2) { + return null; + } + return new CollectFreqSetResponse(frame.data[0], frame.data[1]); + } + + // HrBpBoAutoMeasure响应类 + public static class HrBpBoAutoMeasureResponse { + public byte sn; + public byte status; + + public HrBpBoAutoMeasureResponse(byte sn, byte status) { + this.sn = sn; + this.status = status; + } + } + + public static HrBpBoAutoMeasureResponse parseHrBpBoAutoMeasureResponse(Frame frame) { + if (frame == null || frame.command != Commands.CMD_HRBPBO_AUTO_MEASURE_RESP || frame.data.length < 2) { + return null; + } + return new HrBpBoAutoMeasureResponse(frame.data[0], frame.data[1]); + } + + // HrBpBoManualMeasure响应类 + public static class HrBpBoManualMeasureResponse { + public byte sn; + public byte status; + + public HrBpBoManualMeasureResponse(byte sn, byte status) { + this.sn = sn; + this.status = status; + } + } + + public static HrBpBoManualMeasureResponse parseHrBpBoManualMeasureResponse(Frame frame) { + if (frame == null || frame.command != Commands.CMD_HRBPBO_MANUAL_MEASURE_RESP || frame.data.length < 2) { + return null; + } + return new HrBpBoManualMeasureResponse(frame.data[0], frame.data[1]); + } + + // DynamicMeasure响应类 + public static class DynamicMeasureResponse { + public byte sn; + public byte status; + + public DynamicMeasureResponse(byte sn, byte status) { + this.sn = sn; + this.status = status; + } + } + + public static DynamicMeasureResponse parseDynamicMeasureResponse(Frame frame) { + if (frame == null || frame.command != Commands.CMD_DYNAMIC_MEASURE_RESP || frame.data.length < 2) { + return null; + } + return new DynamicMeasureResponse(frame.data[0], frame.data[1]); + } + + // 创建佩戴检测查询请求帧 [261 0 0] + public static byte[] createWearDetectionRequest() { + return createFrame(Commands.CMD_WEAR_DETECTION, new byte[]{}); + } + + // 佩戴检测响应类 + public static class WearDetectionResponse { + public boolean isWearing; // true表示已佩戴,false表示未佩戴 + + public WearDetectionResponse( boolean isWearing) { + this.isWearing = isWearing; + } + + @Override + public String toString() { + return "WearDetectionResponse{" + + ", isWearing=" + isWearing + + '}'; + } + } + + // 解析佩戴检测响应帧 [262 sn len 0/1] + public static WearDetectionResponse parseWearDetectionResponse(Frame frame) { + if (frame == null || frame.command != Commands.CMD_WEAR_DETECTION_RESP ) { + return null; + } + boolean isWearing = frame.data[0] == 1; + return new WearDetectionResponse(isWearing); + } +} \ 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 new file mode 100644 index 0000000..ead9e09 --- /dev/null +++ b/app/src/main/java/com/ismart/ib86/feature/serial/SerialPort/SerialPortHelper.java @@ -0,0 +1,591 @@ +package com.ismart.ib86.feature.serial.SerialPort; + +import android.serialport.SerialPort; + +import com.ismart.ib86.common.utils.LogManager; + +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Objects; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +public class SerialPortHelper { + private final static String TAG = "SerialPortHelper"; + private SerialPort mSerialPort; + private String mPortName; + private int mBaudRate; + private final BlockingQueue sendQueue = new LinkedBlockingQueue<>(); + private final BlockingQueue receiveQueue = new LinkedBlockingQueue<>(100); + private Thread sendThread; + private Thread receiveThread; + private Thread processThread; + private volatile boolean isRunning; + private SendCallback sendCallback; + private ReceiveCallback receiveCallback; + private DeviceCheckCallback deviceCheckCallback; + private DeviceControlCallback deviceControlCallback; + private LogControlCallback logControlCallback; + private BtVersionCallback btVersionCallback; + private BtUpgradeCallback btUpgradeCallback; + private HostStatusCallback hostStatusCallback; + private BootBinCheckCallback bootBinCheckCallback; + private CollectFreqSetCallback collectFreqSetCallback; + private HrBpBoAutoMeasureCallback hrBpBoAutoMeasureCallback; + private HrBpBoManualMeasureCallback hrBpBoManualMeasureCallback; + private DynamicMeasureCallback dynamicMeasureCallback; + private WearDetectionCallback wearDetectionCallback; + private boolean isSimulationMode = false; + private final ByteBuffer frameBuffer = ByteBuffer.allocate(1024); + + public interface SendCallback { + void onSendSuccess(byte[] data); + + void onSendFailed(byte[] data, Exception e); + } + + public interface ReceiveCallback { + void onDataReceived(short command, byte[] data); + + void onReceiveError(Exception e); + } + + public interface DeviceCheckCallback { + void onDeviceCheckResponse(ASR5515Protocol.DeviceInfo deviceInfo); + } + + public interface DeviceControlCallback { + void onDeviceRestartResponse(boolean success); + } + + public interface LogControlCallback { + void onLogControlResponse(boolean success); + } + + public interface BtVersionCallback { + void onBtVersionResponse(String version); + } + + public interface BtUpgradeCallback { + void onBtUpgradeResponse(ASR5515Protocol.BtUpgradeResponse response); + } + + public interface HostStatusCallback { + void onHostStatusResponse(ASR5515Protocol.HostStatusResponse response); + } + + public interface BootBinCheckCallback { + void onBootBinCheckResponse(ASR5515Protocol.BootBinCheckResponse response); + } + + public interface CollectFreqSetCallback { + void onCollectFreqSetResponse(ASR5515Protocol.CollectFreqSetResponse response); + } + + public interface HrBpBoAutoMeasureCallback { + void onHrBpBoAutoMeasureResponse(ASR5515Protocol.HrBpBoAutoMeasureResponse response); + } + + public interface HrBpBoManualMeasureCallback { + void onHrBpBoManualMeasureResponse(ASR5515Protocol.HrBpBoManualMeasureResponse response); + } + + public interface DynamicMeasureCallback { + void onDynamicMeasureResponse(ASR5515Protocol.DynamicMeasureResponse response); + } + + public interface WearDetectionCallback { + void onWearDetectionResponse(ASR5515Protocol.WearDetectionResponse response); + } + + public SerialPortHelper(String portName, int baudRate) { + this.mPortName = portName; + this.mBaudRate = baudRate; + this.isRunning = false; + this.isSimulationMode = false; + } + + public void setSimulationMode(boolean enabled) { + this.isSimulationMode = enabled; + } + + public void setSendCallback(SendCallback callback) { + this.sendCallback = callback; + } + + public void setReceiveCallback(ReceiveCallback callback) { + this.receiveCallback = callback; + } + + public void setDeviceCheckCallback(DeviceCheckCallback callback) { + this.deviceCheckCallback = callback; + } + + public void setDeviceControlCallback(DeviceControlCallback callback) { + this.deviceControlCallback = callback; + } + + public void setLogControlCallback(LogControlCallback callback) { + this.logControlCallback = callback; + } + + public void setBtVersionCallback(BtVersionCallback callback) { + this.btVersionCallback = callback; + } + + public void setBtUpgradeCallback(BtUpgradeCallback callback) { + this.btUpgradeCallback = callback; + } + + public void setHostStatusCallback(HostStatusCallback callback) { + this.hostStatusCallback = callback; + } + + public void setBootBinCheckCallback(BootBinCheckCallback callback) { + this.bootBinCheckCallback = callback; + } + + public void setCollectFreqSetCallback(CollectFreqSetCallback callback) { + this.collectFreqSetCallback = callback; + } + + public void setHrBpBoAutoMeasureCallback(HrBpBoAutoMeasureCallback callback) { + this.hrBpBoAutoMeasureCallback = callback; + } + + public void setHrBpBoManualMeasureCallback(HrBpBoManualMeasureCallback callback) { + this.hrBpBoManualMeasureCallback = callback; + } + + public void setDynamicMeasureCallback(DynamicMeasureCallback callback) { + this.dynamicMeasureCallback = callback; + } + + public void setWearDetectionCallback(WearDetectionCallback callback) { + this.wearDetectionCallback = callback; + } + + /** + * 发送佩戴检测查询请求 + */ + public void sendWearDetectionRequest() { + byte[] data = ASR5515Protocol.createWearDetectionRequest(); + sendData(data, null); + } + + // ASR-5515协议命令发送方法 + public void checkDevice() { + byte[] data = ASR5515Protocol.createDeviceCheckRequest(); + sendData(data, null); + } + + public void restartDevice() { + byte[] data = ASR5515Protocol.createDeviceRestartRequest(); + sendData(data, null); + } + + public void setLogControl(boolean enable) { + byte[] data = ASR5515Protocol.createLogControlRequest((byte)0, (byte)1, (byte)(enable ? 1 : 0)); + sendData(data, null); + } + + public void queryBtVersion() { + byte[] data = ASR5515Protocol.createBtVersionRequest(); + sendData(data, null); + } + + /** + * 发送蓝牙升级请求 + */ + public void upgradeBt() { + byte[] data = ASR5515Protocol.createBtUpgradeRequest(); + sendData(data, null); + } + + /** + * 发送主机状态同步请求 + * @param screenOn 屏幕状态(true:亮屏,false:灭屏) + */ + public void syncHostStatus(boolean screenOn) { + byte[] data = ASR5515Protocol.createHostStatusRequest(screenOn); + sendData(data, null); + } + + /** + * 发送boot bin检查请求 + */ + public void checkBootBin() { + byte[] data = ASR5515Protocol.createBootBinCheckRequest(); + sendData(data, null); + } + + /** + * 设置采集频率 + * @param len 长度 + * @param freq 频率值 + */ + public void setCollectFreq(byte len, byte freq) { + byte[] data = ASR5515Protocol.createCollectFreqSetRequest(len, freq); + sendData(data, null); + } + + /** + * 发送HrBpBo自动测量请求 + */ + public void startHrBpBoAutoMeasure() { + byte[] data = ASR5515Protocol.createHrBpBoAutoMeasureRequest(); + sendData(data, null); + } + + /** + * 发送HrBpBo手动测量请求 + */ + public void startHrBpBoManualMeasure() { + byte[] data = ASR5515Protocol.createHrBpBoManualMeasureRequest(); + sendData(data, null); + } + + /** + * 发送动态测量请求 + */ + public void startDynamicMeasure() { + byte[] data = ASR5515Protocol.createDynamicMeasureRequest(); + sendData(data, null); + } + + public boolean open() { + try { + LogManager.d(TAG, "Opening serial port: " + mPortName + " with baud rate: " + mBaudRate); + if (!isSimulationMode) { + mSerialPort = new SerialPort(new File(mPortName), mBaudRate); + LogManager.d(TAG, "Serial port opened successfully"); + } else { + LogManager.d(TAG, "Running in simulation mode, skipping actual port opening"); + } + startSendThread(); + startReceiveThread(); + startProcessThread(); + return true; + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + + public void close() { + isRunning = false; + if (sendThread != null) { + sendThread.interrupt(); + try { + sendThread.join(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + sendThread = null; + } + if (receiveThread != null) { + receiveThread.interrupt(); + try { + receiveThread.join(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + receiveThread = null; + } + if (processThread != null) { + processThread.interrupt(); + try { + processThread.join(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + processThread = null; + } + if (mSerialPort != null) { + mSerialPort.closePort(); + mSerialPort = null; + } + } + + public void sendAsync(byte[] data) { + if (data != null) { + sendQueue.offer(data); + } + } + + public void sendData(byte[] data, SendCallback callback) { + if (data == null || data.length == 0) { + if (callback != null) { + callback.onSendFailed(data, new IllegalArgumentException("Data is null or empty")); + } + return; + } + + if (isSimulationMode) { + + if (callback != null) { + callback.onSendSuccess(data); + } + return; + } +// LogManager.d(TAG, "ASR->5515 DATA: " + bytesToHexString(data)); + sendAsync(data); + if (callback != null) { + this.sendCallback = callback; + } + } + + /** + * 将字节数组转换为16进制字符串 + * @param bytes 字节数组 + * @return 16进制字符串,格式如: "01 A2 FF" + */ + public static String bytesToHexString(byte[] bytes) { + if (bytes == null || bytes.length == 0) { + return ""; + } + StringBuilder sb = new StringBuilder(); + for (byte b : bytes) { + sb.append(String.format("%02X ", b)); + } + return sb.toString().trim(); + } + + + private void startSendThread() { + if (sendThread == null) { + LogManager.d(TAG, "Starting send thread"); + isRunning = true; + sendThread = new Thread(() -> { + LogManager.d(TAG, "Send thread started"); + while (isRunning) { + try { + byte[] data = sendQueue.take(); + if (isSimulationMode) { + LogManager.d(TAG, "[SimulationMode] Sending data: " + bytesToHexString(data)); + if (sendCallback != null) { + sendCallback.onSendSuccess(data); + } + } else if (mSerialPort != null) { + try { + LogManager.d(TAG, "ASR->5515 DATA: " + Arrays.toString(data)); + LogManager.d(TAG, "ASR->5515 DATA: " + bytesToHexString(data)); + mSerialPort.getOutputStream().write(data); + if (sendCallback != null) { + sendCallback.onSendSuccess(data); + } + } catch (IOException e) { + //LogManager.e(TAG, "Failed to send data: " + Arrays.toString(data)); + if (sendCallback != null) { + sendCallback.onSendFailed(data, e); + } + } + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + }); + sendThread.start(); + } + } + + private void startReceiveThread() { + if (receiveThread == null) { + receiveThread = new Thread(() -> { + byte[] buffer = new byte[1024]; + while (isRunning) { + if (mSerialPort != null) { + try { + int bytesRead = mSerialPort.getInputStream().read(buffer); + if (bytesRead > 0) { + byte[] receivedData = Arrays.copyOf(buffer, bytesRead); + LogManager.d(TAG, "5515->ASR DATA: " + Arrays.toString(receivedData)); + LogManager.d(TAG, "5515->ASR DATA: " + bytesToHexString(receivedData)); + receiveQueue.put(receivedData); + } + } catch (IOException e) { + if (receiveCallback != null) { + receiveCallback.onReceiveError(e); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + }); + receiveThread.start(); + } + } + + private void startProcessThread() { + if (processThread == null) { + processThread = new Thread(() -> { + while (isRunning) { + try { + byte[] receivedData = receiveQueue.poll(100, TimeUnit.MILLISECONDS); + if (receivedData != null) { + handleReceivedData(receivedData); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + }); + processThread.start(); + } + } + + public int write(byte[] buffer, int size) { + if (mSerialPort != null) { + try { + mSerialPort.getOutputStream().write(buffer, 0, size); + return size; // 返回实际写入的字节数 + } catch (IOException e) { + e.printStackTrace(); + } + } + return -1; + } + + public int read(byte[] buffer, int size) { + if (mSerialPort != null) { + try { + return mSerialPort.getInputStream().read(buffer, 0, size); + } catch (IOException e) { + e.printStackTrace(); + } + } + return -1; + } + + private void handleReceivedData(byte[] data) { + // 将新数据添加到帧缓冲区 + if (frameBuffer.position() + data.length > frameBuffer.capacity()) { + // 缓冲区溢出,清空缓冲区 + frameBuffer.clear(); + LogManager.e(TAG, "Frame buffer overflow, cleared"); + return; + } + frameBuffer.put(data); + + // 尝试解析完整的帧 + while (frameBuffer.position() > 0) { + frameBuffer.flip(); // 切换到读取模式 + byte[] bufferArray = new byte[frameBuffer.remaining()]; + frameBuffer.get(bufferArray); + frameBuffer.clear(); + + ASR5515Protocol.Frame frame = ASR5515Protocol.parseFrame(bufferArray); + if (frame == null) { + // 解析失败,可能是数据不完整,保留数据等待下次处理 + frameBuffer.put(bufferArray); + break; + } + + // 处理解析出的帧 + processFrame(frame); + + // 如果还有剩余数据,放回缓冲区 + if (frame.remainingData != null && frame.remainingData.length > 0) { + frameBuffer.put(frame.remainingData); + } + } + } + + private void processFrame(ASR5515Protocol.Frame frame) { + // 通知原始数据接收回调 + if (receiveCallback != null) { + receiveCallback.onDataReceived(frame.command, frame.data); + } + LogManager.d(TAG,"CMD TYPE:"+frame.command); + // 处理特定类型的响应 + switch (frame.command) { + case ASR5515Protocol.Commands.CMD_CHECK_DEVICE_RESP: + ASR5515Protocol.DeviceInfo deviceInfo = ASR5515Protocol.parseDeviceCheckResponse(frame); + if (deviceInfo != null && deviceCheckCallback != null) { + deviceCheckCallback.onDeviceCheckResponse(deviceInfo); + } + break; + + case ASR5515Protocol.Commands.CMD_RESTART_DEVICE_RESP: + if (deviceControlCallback != null) { + deviceControlCallback.onDeviceRestartResponse(frame.data.length == 0); + } + break; + + case ASR5515Protocol.Commands.CMD_LOG_CONTROL_RESP: + if (logControlCallback != null) { + logControlCallback.onLogControlResponse(frame.data.length == 0); + } + break; + + case ASR5515Protocol.Commands.CMD_BT_VERSION_RESP: + String version = ASR5515Protocol.parseBtVersionResponse(frame); + if (version != null && btVersionCallback != null) { + btVersionCallback.onBtVersionResponse(version); + } + break; + + case ASR5515Protocol.Commands.CMD_BT_UPGRADE_RESP: + ASR5515Protocol.BtUpgradeResponse upgradeResponse = ASR5515Protocol.parseBtUpgradeResponse(frame); + if (upgradeResponse != null && btUpgradeCallback != null) { + btUpgradeCallback.onBtUpgradeResponse(upgradeResponse); + } + break; + + case ASR5515Protocol.Commands.CMD_HOST_STATUS_RESP: + case ASR5515Protocol.Commands.CMD_HOST_STATUS_BOOT_RESP: + ASR5515Protocol.HostStatusResponse statusResponse = ASR5515Protocol.parseHostStatusResponse(frame); + if (statusResponse != null && hostStatusCallback != null) { + hostStatusCallback.onHostStatusResponse(statusResponse); + } + break; + + case ASR5515Protocol.Commands.CMD_BOOT_BIN_CHECK_RESP: + ASR5515Protocol.BootBinCheckResponse bootBinCheckResponse = ASR5515Protocol.parseBootBinCheckResponse(frame); + if (bootBinCheckResponse != null && bootBinCheckCallback != null) { + bootBinCheckCallback.onBootBinCheckResponse(bootBinCheckResponse); + } + break; + + case ASR5515Protocol.Commands.CMD_COLLECT_FREQ_SET_RESP: + ASR5515Protocol.CollectFreqSetResponse collectFreqSetResponse = ASR5515Protocol.parseCollectFreqSetResponse(frame); + if (collectFreqSetResponse != null && collectFreqSetCallback != null) { + collectFreqSetCallback.onCollectFreqSetResponse(collectFreqSetResponse); + } + break; + + case ASR5515Protocol.Commands.CMD_HRBPBO_AUTO_MEASURE_RESP: + ASR5515Protocol.HrBpBoAutoMeasureResponse hrBpBoAutoMeasureResponse = ASR5515Protocol.parseHrBpBoAutoMeasureResponse(frame); + if (hrBpBoAutoMeasureResponse != null && hrBpBoAutoMeasureCallback != null) { + hrBpBoAutoMeasureCallback.onHrBpBoAutoMeasureResponse(hrBpBoAutoMeasureResponse); + } + break; + + case ASR5515Protocol.Commands.CMD_HRBPBO_MANUAL_MEASURE_RESP: + ASR5515Protocol.HrBpBoManualMeasureResponse hrBpBoManualMeasureResponse = ASR5515Protocol.parseHrBpBoManualMeasureResponse(frame); + if (hrBpBoManualMeasureResponse != null && hrBpBoManualMeasureCallback != null) { + hrBpBoManualMeasureCallback.onHrBpBoManualMeasureResponse(hrBpBoManualMeasureResponse); + } + break; + + case ASR5515Protocol.Commands.CMD_DYNAMIC_MEASURE_RESP: + ASR5515Protocol.DynamicMeasureResponse dynamicMeasureResponse = ASR5515Protocol.parseDynamicMeasureResponse(frame); + if (dynamicMeasureResponse != null && dynamicMeasureCallback != null) { + dynamicMeasureCallback.onDynamicMeasureResponse(dynamicMeasureResponse); + } + break; + + case ASR5515Protocol.Commands.CMD_WEAR_DETECTION_RESP: + ASR5515Protocol.WearDetectionResponse wearDetectionResponse = ASR5515Protocol.parseWearDetectionResponse(frame); + if (wearDetectionResponse != null && wearDetectionCallback != null) { + wearDetectionCallback.onWearDetectionResponse(wearDetectionResponse); + } + break; + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ 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 new file mode 100644 index 0000000..5c7b45d --- /dev/null +++ b/app/src/main/res/layout/activity_asr5515_demo.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + +