添加初始工程

This commit is contained in:
helloyifa 2025-06-25 14:38:08 +08:00
parent a5cbb71364
commit dd06e16646
56 changed files with 3923 additions and 36 deletions

48
.gitignore vendored
View File

@ -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

3
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

6
.idea/compiler.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="21" />
</component>
</project>

10
.idea/deploymentTargetSelector.xml generated Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetSelector">
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
</selectionStates>
</component>
</project>

20
.idea/gradle.xml generated Normal file
View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveExternalAnnotations" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>

10
.idea/migrations.xml generated Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectMigrations">
<option name="MigrateToGradleLocalJavaHome">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</component>
</project>

10
.idea/misc.xml generated Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

17
.idea/runConfigurations.xml generated Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
</set>
</option>
</component>
</project>

146
README.md
View File

@ -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设备支持相应传感器

1
app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

49
app/build.gradle Normal file
View File

@ -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
}

Binary file not shown.

21
app/proguard-rules.pro vendored Normal file
View File

@ -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

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.ismart.ib86.app">
<!-- 添加读写权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
tools:ignore="ProtectedPermissions" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.IB86"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -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();
}

View File

@ -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<Driver> mDrivers = null;
Vector<Driver> getDrivers() throws IOException {
if (mDrivers == null) {
mDrivers = new Vector<Driver>();
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<String> devices = new Vector<String>();
Iterator<Driver> itdriv;
try {
itdriv = getDrivers().iterator();
while (itdriv.hasNext()) {
Driver driver = itdriv.next();
Iterator<File> 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<String> devices = new Vector<String>();
Iterator<Driver> itdriv;
try {
itdriv = getDrivers().iterator();
while (itdriv.hasNext()) {
Driver driver = itdriv.next();
Iterator<File> 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<File> mDevices = null;
private String mDriverName;
private String mDeviceRoot;
public Driver(String name, String root) {
mDriverName = name;
mDeviceRoot = root;
}
public Vector<File> getDevices() {
if (mDevices == null) {
mDevices = new Vector<File>();
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;
}
}
}

View File

@ -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, "部分权限被拒绝,可能影响应用功能");
}
}
}
}

View File

@ -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);
}
}

View File

@ -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<String, File> 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());
}
}
}
}
}

View File

@ -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();
}
}
}

View File

@ -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());
}
}
}

View File

@ -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 {
// 提取type2字节
short type = (short)(((data[startIndex + 2] & 0xFF) << 8) | (data[startIndex + 1] & 0xFF));
// 提取sn2字节
short sn = (short)(((data[startIndex + 4] & 0xFF) << 8) | (data[startIndex + 3] & 0xFF));
// 提取len2字节
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);
}
}

View File

@ -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<byte[]> sendQueue = new LinkedBlockingQueue<>();
private final BlockingQueue<byte[]> 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;
}
}
}

View File

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View File

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="设备信息"
android:textSize="16sp"
android:textStyle="bold"
android:layout_marginBottom="8dp"/>
<TextView
android:id="@+id/tv_device_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#F5F5F5"
android:padding="8dp"
android:textSize="14sp"
android:minHeight="100dp"
android:layout_marginBottom="16dp"/>
<TextView
android:id="@+id/tv_bt_version"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#F5F5F5"
android:padding="8dp"
android:textSize="14sp"
android:layout_marginBottom="16dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="8dp">
<Button
android:id="@+id/btn_check_device"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="检查设备"
android:layout_marginEnd="8dp"/>
<Button
android:id="@+id/btn_restart_device"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="重启设备"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="8dp">
<Button
android:id="@+id/btn_query_bt_version"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="查询蓝牙版本"
android:layout_marginEnd="8dp"/>
<ToggleButton
android:id="@+id/toggle_log"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textOn="关闭日志"
android:textOff="开启日志"/>
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
tools:context=".MainActivity">
<TextView
android:id="@+id/titleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="iB86 功能演示"
android:textSize="24sp"
android:textStyle="bold"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="32dp" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<TextView
android:id="@+id/titleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="佩戴检测"
android:textSize="24sp"
android:textStyle="bold"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="16dp" />
<Button
android:id="@+id/detectButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="检测佩戴状态"
app:layout_constraintTop_toBottomOf="@id/titleTextView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="32dp" />
<TextView
android:id="@+id/statusLabelTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="佩戴状态:"
android:textSize="18sp"
app:layout_constraintTop_toBottomOf="@id/detectButton"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="32dp" />
<TextView
android:id="@+id/statusTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="未检测"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintTop_toTopOf="@id/statusLabelTextView"
app:layout_constraintStart_toEndOf="@id/statusLabelTextView"
android:layout_marginStart="8dp" />
<TextView
android:id="@+id/logTextView"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#F5F5F5"
android:padding="8dp"
android:scrollbars="vertical"
android:gravity="top|start"
app:layout_constraintTop_toBottomOf="@id/statusLabelTextView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginTop="32dp" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -0,0 +1,7 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Base.Theme.IB86" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Customize your dark theme here. -->
<!-- <item name="colorPrimary">@color/my_dark_primary</item> -->
</style>
</resources>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name">iB86</string>
</resources>

View File

@ -0,0 +1,9 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Base.Theme.IB86" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Customize your light theme here. -->
<!-- <item name="colorPrimary">@color/my_light_primary</item> -->
</style>
<style name="Theme.IB86" parent="Base.Theme.IB86" />
</resources>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample backup rules file; uncomment and customize as necessary.
See https://developer.android.com/guide/topics/data/autobackup
for details.
Note: This file is ignored for devices older that API 31
See https://developer.android.com/about/versions/12/backup-restore
-->
<full-backup-content>
<!--
<include domain="sharedpref" path="."/>
<exclude domain="sharedpref" path="device.xml"/>
-->
</full-backup-content>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample data extraction rules file; uncomment and customize as necessary.
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
for details.
-->
<data-extraction-rules>
<cloud-backup>
<!-- TODO: Use <include> and <exclude> to control what is backed up.
<include .../>
<exclude .../>
-->
</cloud-backup>
<!--
<device-transfer>
<include .../>
<exclude .../>
</device-transfer>
-->
</data-extraction-rules>

4
build.gradle Normal file
View File

@ -0,0 +1,4 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
alias(libs.plugins.android.application) apply false
}

Binary file not shown.

BIN
docs/iB86.zip Normal file

Binary file not shown.

21
gradle.properties Normal file
View File

@ -0,0 +1,21 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. For more details, visit
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true

22
gradle/libs.versions.toml Normal file
View File

@ -0,0 +1,22 @@
[versions]
agp = "8.7.2"
junit = "4.13.2"
junitVersion = "1.1.5"
espressoCore = "3.5.1"
appcompat = "1.6.1"
material = "1.10.0"
activity = "1.8.0"
constraintlayout = "2.1.4"
[libraries]
junit = { group = "junit", name = "junit", version.ref = "junit" }
ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,6 @@
#Thu May 29 08:58:17 GMT+08:00 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

185
gradlew vendored Normal file
View File

@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# 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
#
# https://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.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

89
gradlew.bat vendored Normal file
View File

@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

23
settings.gradle Normal file
View File

@ -0,0 +1,23 @@
pluginManagement {
repositories {
google {
content {
includeGroupByRegex("com\\.android.*")
includeGroupByRegex("com\\.google.*")
includeGroupByRegex("androidx.*")
}
}
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "iB86"
include ':app'