添加初始工程
48
.gitignore
vendored
@ -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
|
*.iml
|
||||||
.idea/
|
.gradle
|
||||||
misc.xml
|
/local.properties
|
||||||
deploymentTargetDropDown.xml
|
/.idea/caches
|
||||||
render.experimental.xml
|
/.idea/libraries
|
||||||
|
/.idea/modules.xml
|
||||||
# Keystore files
|
/.idea/workspace.xml
|
||||||
*.jks
|
/.idea/navEditor.xml
|
||||||
*.keystore
|
/.idea/assetWizardSettings.xml
|
||||||
|
.DS_Store
|
||||||
# Google Services (e.g. APIs or Firebase)
|
/build
|
||||||
google-services.json
|
/captures
|
||||||
|
.externalNativeBuild
|
||||||
# Android Profiling
|
.cxx
|
||||||
*.hprof
|
local.properties
|
||||||
|
|
||||||
|
3
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
6
.idea/compiler.xml
generated
Normal 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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -0,0 +1 @@
|
|||||||
|
/build
|
49
app/build.gradle
Normal 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
|
||||||
|
}
|
BIN
app/libs/armeabi-v7a/libserial_port.so
Normal file
21
app/proguard-rules.pro
vendored
Normal 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
|
33
app/src/main/AndroidManifest.xml
Normal 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>
|
89
app/src/main/java/android/serialport/SerialPort.java
Normal 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();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
125
app/src/main/java/android/serialport/SerialPortFinder.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
297
app/src/main/java/com/ismart/ib86/app/MainActivity.java
Normal 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, "部分权限被拒绝,可能影响应用功能");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
122
app/src/main/java/com/ismart/ib86/common/utils/LogManager.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
368
app/src/main/java/com/ismart/ib86/feature/gpio/GpioManager.java
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
132
app/src/main/java/com/ismart/ib86/feature/gpio/GpioTest.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,648 @@
|
|||||||
|
package com.ismart.ib86.feature.serial.SerialPort;
|
||||||
|
|
||||||
|
import static com.ismart.ib86.feature.serial.SerialPort.SerialPortHelper.bytesToHexString;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.ismart.ib86.common.utils.LogManager;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HexFormat;
|
||||||
|
|
||||||
|
public class ASR5515Protocol {
|
||||||
|
private static final String TAG = "ASR5515Protocol";
|
||||||
|
// 设备检查回调接口
|
||||||
|
public interface DeviceCheckCallback {
|
||||||
|
void onDeviceCheckResponse(DeviceInfo deviceInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设备控制回调接口
|
||||||
|
public interface DeviceControlCallback {
|
||||||
|
void onDeviceRestartResponse(boolean success);
|
||||||
|
void onLogControlResponse(boolean success);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 蓝牙版本回调接口
|
||||||
|
public interface BtVersionCallback {
|
||||||
|
void onBtVersionResponse(String version);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 蓝牙升级回调接口
|
||||||
|
public interface BtUpgradeCallback {
|
||||||
|
void onBtUpgradeResponse(BtUpgradeResponse response);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主机状态回调接口
|
||||||
|
public interface HostStatusCallback {
|
||||||
|
void onHostStatusResponse(HostStatusResponse response);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Boot Bin检查回调接口
|
||||||
|
public interface BootBinCheckCallback {
|
||||||
|
void onBootBinCheckResponse(BootBinCheckResponse response);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 采集频率设置回调接口
|
||||||
|
public interface CollectFreqSetCallback {
|
||||||
|
void onCollectFreqSetResponse(CollectFreqSetResponse response);
|
||||||
|
}
|
||||||
|
|
||||||
|
// HrBpBo自动测量回调接口
|
||||||
|
public interface HrBpBoAutoMeasureCallback {
|
||||||
|
void onHrBpBoAutoMeasureResponse(HrBpBoAutoMeasureResponse response);
|
||||||
|
}
|
||||||
|
|
||||||
|
// HrBpBo手动测量回调接口
|
||||||
|
public interface HrBpBoManualMeasureCallback {
|
||||||
|
void onHrBpBoManualMeasureResponse(HrBpBoManualMeasureResponse response);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 动态测量回调接口
|
||||||
|
public interface DynamicMeasureCallback {
|
||||||
|
void onDynamicMeasureResponse(DynamicMeasureResponse response);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 佩戴检测回调接口
|
||||||
|
public interface WearDetectionCallback {
|
||||||
|
void onWearDetectionResponse(WearDetectionResponse response);
|
||||||
|
}
|
||||||
|
private static final byte FRAME_START = (byte) '[';
|
||||||
|
private static final byte FRAME_END = (byte) ']';
|
||||||
|
|
||||||
|
public static class DeviceInfo {
|
||||||
|
public byte sn; // 序列号
|
||||||
|
public byte len; // 长度
|
||||||
|
public byte heartRate; // 心率
|
||||||
|
public byte heartRateId; // 心率ID
|
||||||
|
public byte stepCount; // 计步
|
||||||
|
public byte stepCountId; // 计步ID
|
||||||
|
public String deviceId; // 设备ID
|
||||||
|
public String hardwareVersion; // 硬件版本
|
||||||
|
public String softwareVersion; // 软件版本
|
||||||
|
public String status; // 设备状态
|
||||||
|
|
||||||
|
public DeviceInfo(byte sn, byte len, byte heartRate, byte heartRateId, byte stepCount, byte stepCountId,
|
||||||
|
String deviceId, String hardwareVersion, String softwareVersion, String status) {
|
||||||
|
this.sn = sn;
|
||||||
|
this.len = len;
|
||||||
|
this.heartRate = heartRate;
|
||||||
|
this.heartRateId = heartRateId;
|
||||||
|
this.stepCount = stepCount;
|
||||||
|
this.stepCountId = stepCountId;
|
||||||
|
this.deviceId = deviceId;
|
||||||
|
this.hardwareVersion = hardwareVersion;
|
||||||
|
this.softwareVersion = softwareVersion;
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("设备信息:\n设备ID: %s\n硬件版本: %s\n软件版本: %s\n状态: %s",
|
||||||
|
deviceId, hardwareVersion, softwareVersion, status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第一个parseDeviceCheckResponse方法被删除,保留文件末尾的更详细实现
|
||||||
|
|
||||||
|
public static class RebootResponse {
|
||||||
|
public byte sn;
|
||||||
|
public byte status;
|
||||||
|
|
||||||
|
public RebootResponse(byte sn, byte status) {
|
||||||
|
this.sn = sn;
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RebootResponse parseRebootResponse(Frame frame) {
|
||||||
|
if (frame == null || frame.command != Commands.CMD_DEVICE_REBOOT_RESP || frame.data.length < 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new RebootResponse(frame.data[0], frame.data[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TimeSyncResponse {
|
||||||
|
public byte sn;
|
||||||
|
public byte status;
|
||||||
|
|
||||||
|
public TimeSyncResponse(byte sn, byte status) {
|
||||||
|
this.sn = sn;
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TimeSyncResponse parseTimeSyncResponse(Frame frame) {
|
||||||
|
if (frame == null || frame.command != Commands.CMD_TIME_SYNC_RESP || frame.data.length < 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new TimeSyncResponse(frame.data[0], frame.data[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class LogControlResponse {
|
||||||
|
public byte sn;
|
||||||
|
public byte status;
|
||||||
|
|
||||||
|
public LogControlResponse(byte sn, byte status) {
|
||||||
|
this.sn = sn;
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LogControlResponse parseLogControlResponse(Frame frame) {
|
||||||
|
if (frame == null || frame.command != Commands.CMD_LOG_CONTROL_RESP || frame.data.length < 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new LogControlResponse(frame.data[0], frame.data[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建设备重启请求帧
|
||||||
|
public static byte[] createRebootRequest(byte sn) {
|
||||||
|
return createFrame(Commands.CMD_DEVICE_REBOOT, new byte[]{sn, 0});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建时间同步请求帧
|
||||||
|
public static byte[] createTimeSyncRequest(byte sn, byte len, int timestamp) {
|
||||||
|
byte[] data = new byte[6];
|
||||||
|
data[0] = sn;
|
||||||
|
data[1] = len;
|
||||||
|
// 将timestamp转换为4字节数组
|
||||||
|
data[2] = (byte) (timestamp >> 24);
|
||||||
|
data[3] = (byte) (timestamp >> 16);
|
||||||
|
data[4] = (byte) (timestamp >> 8);
|
||||||
|
data[5] = (byte) timestamp;
|
||||||
|
return createFrame(Commands.CMD_TIME_SYNC, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建日志打印控制请求帧
|
||||||
|
public static byte[] createLogControlRequest(byte sn, byte len, byte enable) {
|
||||||
|
return createFrame(Commands.CMD_LOG_CONTROL, new byte[]{sn, len, enable});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Commands {
|
||||||
|
public static final short CMD_CHECK_DEVICE = 0x0065; // 设备检查请求
|
||||||
|
public static final short CMD_CHECK_DEVICE_RESP = 0x0066; // 设备检查响应
|
||||||
|
public static final short CMD_DEVICE_REBOOT = 0x0067; // 设备重启请求
|
||||||
|
public static final short CMD_DEVICE_REBOOT_RESP = 0x0068;// 设备重启响应
|
||||||
|
public static final short CMD_TIME_SYNC = 0x0069; // 时间同步请求
|
||||||
|
public static final short CMD_TIME_SYNC_RESP = 0x006A; // 时间同步响应
|
||||||
|
public static final short CMD_RESTART_DEVICE = 0x0067; // 设备重启请求
|
||||||
|
public static final short CMD_RESTART_DEVICE_RESP = 0x0068; // 设备重启响应
|
||||||
|
public static final short CMD_LOG_CONTROL = 0x006B; // 日志打印控制请求
|
||||||
|
public static final short CMD_LOG_CONTROL_RESP = 0x006C; // 日志打印控制响应
|
||||||
|
public static final short CMD_BT_VERSION = 0x006D; // 蓝牙版本请求
|
||||||
|
public static final short CMD_BT_VERSION_RESP = 0x006E; // 蓝牙版本响应
|
||||||
|
public static final short CMD_BT_UPGRADE = 0x0071; // 本地、蓝牙升级请求
|
||||||
|
public static final short CMD_BT_UPGRADE_RESP = 0x0078; // 本地、蓝牙升级响应
|
||||||
|
public static final short CMD_HOST_STATUS = 0x0079; // 主机状态同步请求
|
||||||
|
public static final short CMD_HOST_STATUS_RESP = 0x007A; // 主机状态同步响应
|
||||||
|
public static final short CMD_HOST_STATUS_BOOT_RESP = 0x007C; //主机状态,启动状态
|
||||||
|
// 新增命令
|
||||||
|
public static final short CMD_BOOT_BIN_CHECK = 0x007D; // boot bin检查请求 [125 0 0]
|
||||||
|
public static final short CMD_BOOT_BIN_CHECK_RESP = 0x007E; // boot bin检查响应 [126 sn len 0/1]
|
||||||
|
public static final short CMD_COLLECT_FREQ_SET = 0x00C9; // 采集频率设置 [201 0 len 32]
|
||||||
|
public static final short CMD_COLLECT_FREQ_SET_RESP = 0x00CA; // 采集频率设置响应 [202 sn 0]
|
||||||
|
public static final short CMD_HRBPBO_AUTO_MEASURE = 0x00CB; // HrBpBo自动测量 [203 0 0]
|
||||||
|
public static final short CMD_HRBPBO_AUTO_MEASURE_RESP = 0x00CC; // HrBpBo自动测量响应 [204 sn 0]
|
||||||
|
public static final short CMD_HRBPBO_MANUAL_MEASURE = 0x00CD; // HrBpBo手动测量 [205 0 0]
|
||||||
|
public static final short CMD_HRBPBO_MANUAL_MEASURE_RESP = 0x00CE; // HrBpBo手动测量响应 [206 sn 0]
|
||||||
|
public static final short CMD_DYNAMIC_MEASURE = 0x00CF; // 动态测量 [207 0 0]
|
||||||
|
public static final short CMD_DYNAMIC_MEASURE_RESP = 0x00D0; // 动态测量响应 [208 sn 0]
|
||||||
|
public static final short CMD_WEAR_DETECTION = 0x0105; // 佩戴检测查询 [261 0 0]
|
||||||
|
public static final short CMD_WEAR_DETECTION_RESP = 0x0106; // 佩戴检测响应 [262 sn len 0/1]
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Frame {
|
||||||
|
public short command; // 修改为short类型以支持2字节命令
|
||||||
|
public byte[] data;
|
||||||
|
public byte[] remainingData; // 解析后剩余的数据
|
||||||
|
|
||||||
|
public Frame(short command, byte[] data) {
|
||||||
|
this.command = command;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] createDeviceCheckRequest() {
|
||||||
|
// 发送格式:[101 0 0]
|
||||||
|
byte[] data = new byte[2]; // 两个0
|
||||||
|
data[0] = 0;
|
||||||
|
data[1] = 0;
|
||||||
|
return createFrame(Commands.CMD_CHECK_DEVICE, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] createFrame(short command, byte[] data) {
|
||||||
|
LogManager.d(TAG, "LEN :" + data.length);
|
||||||
|
int frameLength = 1 + 2 + 2 + 2 + data.length + 1; // start + command(2bytes) + sn(2bytes) + len(2bytes) + data + end
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate(frameLength);
|
||||||
|
|
||||||
|
buffer.put(FRAME_START); // 添加帧起始符'['
|
||||||
|
buffer.put((byte)(command & 0xFF)); // 命令字节低位
|
||||||
|
buffer.put((byte)((command >> 8) & 0xFF)); // 命令字节高位
|
||||||
|
buffer.put((byte)0x00); // sn
|
||||||
|
buffer.put((byte)0x00); //sn
|
||||||
|
buffer.put((byte)(data.length & 0xFF)); // len
|
||||||
|
buffer.put((byte)((data.length >> 8) & 0xFF)); //len
|
||||||
|
if(data.length > 0){
|
||||||
|
buffer.put(data); // 数据部分
|
||||||
|
}
|
||||||
|
buffer.put(FRAME_END); // 添加帧结束符']'
|
||||||
|
|
||||||
|
return buffer.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Frame parseFrame(byte[] data) {
|
||||||
|
if (data == null || data.length < 8) { // 最小帧长度:head(1) + type(2) + sn(2) + len(2) + end(1)
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找帧起始符'['
|
||||||
|
int startIndex = -1;
|
||||||
|
for (int i = 0; i < data.length; i++) {
|
||||||
|
if (data[i] == FRAME_START) {
|
||||||
|
startIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startIndex == -1 || startIndex + 7 >= data.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从起始符后开始查找帧结束符']'
|
||||||
|
int endIndex = -1;
|
||||||
|
for (int i = startIndex + 7; i < data.length; i++) {
|
||||||
|
if (data[i] == FRAME_END) {
|
||||||
|
endIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endIndex == -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 提取type(2字节)
|
||||||
|
short type = (short)(((data[startIndex + 2] & 0xFF) << 8) | (data[startIndex + 1] & 0xFF));
|
||||||
|
|
||||||
|
// 提取sn(2字节)
|
||||||
|
short sn = (short)(((data[startIndex + 4] & 0xFF) << 8) | (data[startIndex + 3] & 0xFF));
|
||||||
|
|
||||||
|
// 提取len(2字节)
|
||||||
|
short len = (short)(((data[startIndex + 6] & 0xFF) << 8) | (data[startIndex + 5] & 0xFF));
|
||||||
|
|
||||||
|
// 提取数据部分(如果有的话)
|
||||||
|
int dataLength = endIndex - (startIndex + 7); // 7 = head(1) + type(2) + sn(2) + len(2)
|
||||||
|
//LogManager.d(TAG, "len: "+ len + " dataLength: " + dataLength + " end data" + data[endIndex]);
|
||||||
|
byte[] frameData;
|
||||||
|
if (dataLength > 0) {
|
||||||
|
frameData = new byte[dataLength];
|
||||||
|
System.arraycopy(data, startIndex + 7, frameData, 0, dataLength);
|
||||||
|
// LogManager.d(TAG,"data :"+ bytesToHexString(frameData));
|
||||||
|
} else {
|
||||||
|
frameData = new byte[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建帧对象
|
||||||
|
Frame frame = new Frame(type, frameData);
|
||||||
|
|
||||||
|
// 如果还有剩余数据,保存到remainingData
|
||||||
|
if (endIndex + 1 < data.length) {
|
||||||
|
frame.remainingData = new byte[data.length - endIndex - 1];
|
||||||
|
System.arraycopy(data, endIndex + 1, frame.remainingData, 0, frame.remainingData.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return frame;
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogManager.e(TAG, "Parse frame error: " + e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DeviceInfo parseDeviceCheckResponse(Frame frame) {
|
||||||
|
if (frame == null || frame.command != Commands.CMD_CHECK_DEVICE_RESP || frame.data.length < 7) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(frame.data);
|
||||||
|
|
||||||
|
// 解析响应数据:[sn len 心率 ID 计步 ID 设备ID]
|
||||||
|
byte sn = buffer.get(); // 序列号
|
||||||
|
byte len = buffer.get(); // 长度
|
||||||
|
byte heartRate = buffer.get(); // 心率
|
||||||
|
byte heartRateId = buffer.get(); // 心率ID
|
||||||
|
byte stepCount = buffer.get(); // 计步
|
||||||
|
byte stepCountId = buffer.get(); // 计步ID
|
||||||
|
byte rawDeviceId = buffer.get(); // 设备ID
|
||||||
|
|
||||||
|
// 将deviceId转换为十六进制字符串
|
||||||
|
String deviceId = String.format("%02X", rawDeviceId);
|
||||||
|
|
||||||
|
// 硬件版本和软件版本可能不在帧数据中,设置默认值
|
||||||
|
String hardwareVersion = "V1.0";
|
||||||
|
String softwareVersion = "V1.0";
|
||||||
|
String status = "正常"; // 默认状态
|
||||||
|
|
||||||
|
// 如果帧数据中包含更多信息,可以继续解析
|
||||||
|
if (frame.data.length > 7) {
|
||||||
|
// 这里需要根据实际协议格式解析硬件版本、软件版本和状态
|
||||||
|
byte statusByte = frame.data[7];
|
||||||
|
status = parseStatus(statusByte);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DeviceInfo(sn, len, heartRate, heartRateId, stepCount, stepCountId, deviceId, hardwareVersion, softwareVersion, status);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String parseStatus(byte statusByte) {
|
||||||
|
switch(statusByte) {
|
||||||
|
case 0:
|
||||||
|
return "正常";
|
||||||
|
case 1:
|
||||||
|
return "异常";
|
||||||
|
case 2:
|
||||||
|
return "离线";
|
||||||
|
default:
|
||||||
|
return "未知状态(" + statusByte + ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建蓝牙版本请求帧
|
||||||
|
public static byte[] createBtVersionRequest() {
|
||||||
|
return createFrame(Commands.CMD_BT_VERSION, new byte[]{});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建设备重启请求帧
|
||||||
|
public static byte[] createDeviceRestartRequest() {
|
||||||
|
return createFrame(Commands.CMD_RESTART_DEVICE, new byte[]{});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析设备重启响应帧
|
||||||
|
public static boolean parseDeviceRestartResponse(Frame frame) {
|
||||||
|
return frame != null && frame.command == Commands.CMD_RESTART_DEVICE_RESP;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String parseBtVersionResponse(Frame frame) {
|
||||||
|
if (frame == null || frame.command != Commands.CMD_BT_VERSION_RESP || frame.data.length == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (byte b : frame.data) {
|
||||||
|
// 只保留可打印的 ASCII 字符(32-126)
|
||||||
|
if (b >= 32 && b <= 126) {
|
||||||
|
sb.append((char) b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return sb.toString();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建本地、蓝牙升级请求帧
|
||||||
|
public static byte[] createBtUpgradeRequest() {
|
||||||
|
// 发送格式:[113 0 0]
|
||||||
|
byte[] data = new byte[2]; // 两个0
|
||||||
|
data[0] = 0;
|
||||||
|
data[1] = 0;
|
||||||
|
return createFrame(Commands.CMD_BT_UPGRADE, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析本地、蓝牙升级响应帧
|
||||||
|
public static class BtUpgradeResponse {
|
||||||
|
public byte sn;
|
||||||
|
public byte len;
|
||||||
|
public String data;
|
||||||
|
|
||||||
|
public BtUpgradeResponse(byte sn, byte len, String data) {
|
||||||
|
this.sn = sn;
|
||||||
|
this.len = len;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BtUpgradeResponse parseBtUpgradeResponse(Frame frame) {
|
||||||
|
if (frame == null || frame.command != Commands.CMD_BT_UPGRADE_RESP || frame.data.length < 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
byte sn = frame.data[0];
|
||||||
|
byte len = frame.data[1];
|
||||||
|
|
||||||
|
// 提取数据部分(如果有的话)
|
||||||
|
String data = "";
|
||||||
|
if (frame.data.length > 2) {
|
||||||
|
byte[] dataBytes = new byte[frame.data.length - 2];
|
||||||
|
System.arraycopy(frame.data, 2, dataBytes, 0, dataBytes.length);
|
||||||
|
data = new String(dataBytes, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new BtUpgradeResponse(sn, len, data);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建主机状态同步请求帧
|
||||||
|
public static byte[] createHostStatusRequest(boolean screenOn) {
|
||||||
|
// 发送格式:[121 0 len 0/1],其中0/1表示屏幕状态(灭/亮)
|
||||||
|
byte[] data = new byte[3];
|
||||||
|
data[0] = 0; // 保留字节
|
||||||
|
data[1] = 1;
|
||||||
|
data[2] = (byte)(screenOn ? 1 : 0); // 1表示亮屏,0表示灭屏
|
||||||
|
return createFrame(Commands.CMD_HOST_STATUS, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建boot bin检查请求帧 [125 0 0]
|
||||||
|
public static byte[] createBootBinCheckRequest() {
|
||||||
|
byte[] data = new byte[2];
|
||||||
|
data[0] = 0;
|
||||||
|
data[1] = 0;
|
||||||
|
return createFrame(Commands.CMD_BOOT_BIN_CHECK, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建采集频率设置请求帧 [201 0 len 32]
|
||||||
|
public static byte[] createCollectFreqSetRequest(byte len, byte freq) {
|
||||||
|
byte[] data = new byte[3];
|
||||||
|
data[0] = 0; // 保留字节
|
||||||
|
data[1] = len;
|
||||||
|
data[2] = freq;
|
||||||
|
return createFrame(Commands.CMD_COLLECT_FREQ_SET, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建HrBpBo自动测量请求帧 [203 0 0]
|
||||||
|
public static byte[] createHrBpBoAutoMeasureRequest() {
|
||||||
|
byte[] data = new byte[2];
|
||||||
|
data[0] = 0;
|
||||||
|
data[1] = 0;
|
||||||
|
return createFrame(Commands.CMD_HRBPBO_AUTO_MEASURE, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建HrBpBo手动测量请求帧 [205 0 0]
|
||||||
|
public static byte[] createHrBpBoManualMeasureRequest() {
|
||||||
|
byte[] data = new byte[2];
|
||||||
|
data[0] = 0;
|
||||||
|
data[1] = 0;
|
||||||
|
return createFrame(Commands.CMD_HRBPBO_MANUAL_MEASURE, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建动态测量请求帧 [207 0 0]
|
||||||
|
public static byte[] createDynamicMeasureRequest() {
|
||||||
|
byte[] data = new byte[2];
|
||||||
|
data[0] = 0;
|
||||||
|
data[1] = 0;
|
||||||
|
return createFrame(Commands.CMD_DYNAMIC_MEASURE, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析主机状态同步响应帧
|
||||||
|
public static class HostStatusResponse {
|
||||||
|
public byte sn;
|
||||||
|
public byte status;
|
||||||
|
|
||||||
|
public HostStatusResponse(byte sn, byte status) {
|
||||||
|
this.sn = sn;
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HostStatusResponse parseHostStatusResponse(Frame frame) {
|
||||||
|
if (frame == null || frame.data.length < 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new HostStatusResponse(frame.data[0], frame.data[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// BootBinCheck响应类
|
||||||
|
public static class BootBinCheckResponse {
|
||||||
|
public byte sn;
|
||||||
|
public byte len;
|
||||||
|
public byte status;
|
||||||
|
|
||||||
|
public BootBinCheckResponse(byte sn, byte len, byte status) {
|
||||||
|
this.sn = sn;
|
||||||
|
this.len = len;
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BootBinCheckResponse parseBootBinCheckResponse(Frame frame) {
|
||||||
|
if (frame == null || frame.command != Commands.CMD_BOOT_BIN_CHECK_RESP || frame.data.length < 3) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new BootBinCheckResponse(frame.data[0], frame.data[1], frame.data[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CollectFreqSet响应类
|
||||||
|
public static class CollectFreqSetResponse {
|
||||||
|
public byte sn;
|
||||||
|
public byte status;
|
||||||
|
|
||||||
|
public CollectFreqSetResponse(byte sn, byte status) {
|
||||||
|
this.sn = sn;
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CollectFreqSetResponse parseCollectFreqSetResponse(Frame frame) {
|
||||||
|
if (frame == null || frame.command != Commands.CMD_COLLECT_FREQ_SET_RESP || frame.data.length < 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new CollectFreqSetResponse(frame.data[0], frame.data[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// HrBpBoAutoMeasure响应类
|
||||||
|
public static class HrBpBoAutoMeasureResponse {
|
||||||
|
public byte sn;
|
||||||
|
public byte status;
|
||||||
|
|
||||||
|
public HrBpBoAutoMeasureResponse(byte sn, byte status) {
|
||||||
|
this.sn = sn;
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HrBpBoAutoMeasureResponse parseHrBpBoAutoMeasureResponse(Frame frame) {
|
||||||
|
if (frame == null || frame.command != Commands.CMD_HRBPBO_AUTO_MEASURE_RESP || frame.data.length < 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new HrBpBoAutoMeasureResponse(frame.data[0], frame.data[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// HrBpBoManualMeasure响应类
|
||||||
|
public static class HrBpBoManualMeasureResponse {
|
||||||
|
public byte sn;
|
||||||
|
public byte status;
|
||||||
|
|
||||||
|
public HrBpBoManualMeasureResponse(byte sn, byte status) {
|
||||||
|
this.sn = sn;
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HrBpBoManualMeasureResponse parseHrBpBoManualMeasureResponse(Frame frame) {
|
||||||
|
if (frame == null || frame.command != Commands.CMD_HRBPBO_MANUAL_MEASURE_RESP || frame.data.length < 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new HrBpBoManualMeasureResponse(frame.data[0], frame.data[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// DynamicMeasure响应类
|
||||||
|
public static class DynamicMeasureResponse {
|
||||||
|
public byte sn;
|
||||||
|
public byte status;
|
||||||
|
|
||||||
|
public DynamicMeasureResponse(byte sn, byte status) {
|
||||||
|
this.sn = sn;
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DynamicMeasureResponse parseDynamicMeasureResponse(Frame frame) {
|
||||||
|
if (frame == null || frame.command != Commands.CMD_DYNAMIC_MEASURE_RESP || frame.data.length < 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new DynamicMeasureResponse(frame.data[0], frame.data[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建佩戴检测查询请求帧 [261 0 0]
|
||||||
|
public static byte[] createWearDetectionRequest() {
|
||||||
|
return createFrame(Commands.CMD_WEAR_DETECTION, new byte[]{});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 佩戴检测响应类
|
||||||
|
public static class WearDetectionResponse {
|
||||||
|
public boolean isWearing; // true表示已佩戴,false表示未佩戴
|
||||||
|
|
||||||
|
public WearDetectionResponse( boolean isWearing) {
|
||||||
|
this.isWearing = isWearing;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "WearDetectionResponse{" +
|
||||||
|
", isWearing=" + isWearing +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析佩戴检测响应帧 [262 sn len 0/1]
|
||||||
|
public static WearDetectionResponse parseWearDetectionResponse(Frame frame) {
|
||||||
|
if (frame == null || frame.command != Commands.CMD_WEAR_DETECTION_RESP ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
boolean isWearing = frame.data[0] == 1;
|
||||||
|
return new WearDetectionResponse(isWearing);
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
170
app/src/main/res/drawable/ic_launcher_background.xml
Normal 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>
|
30
app/src/main/res/drawable/ic_launcher_foreground.xml
Normal 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>
|
82
app/src/main/res/layout/activity_asr5515_demo.xml
Normal 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>
|
23
app/src/main/res/layout/activity_main.xml
Normal 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>
|
66
app/src/main/res/layout/activity_wear_detection.xml
Normal 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>
|
6
app/src/main/res/mipmap-anydpi/ic_launcher.xml
Normal 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>
|
6
app/src/main/res/mipmap-anydpi/ic_launcher_round.xml
Normal 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>
|
BIN
app/src/main/res/mipmap-hdpi/ic_launcher.webp
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher.webp
Normal file
After Width: | Height: | Size: 982 B |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.webp
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Normal file
After Width: | Height: | Size: 5.8 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Normal file
After Width: | Height: | Size: 7.6 KiB |
7
app/src/main/res/values-night/themes.xml
Normal 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>
|
5
app/src/main/res/values/colors.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="black">#FF000000</color>
|
||||||
|
<color name="white">#FFFFFFFF</color>
|
||||||
|
</resources>
|
3
app/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<resources>
|
||||||
|
<string name="app_name">iB86</string>
|
||||||
|
</resources>
|
9
app/src/main/res/values/themes.xml
Normal 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>
|
13
app/src/main/res/xml/backup_rules.xml
Normal 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>
|
19
app/src/main/res/xml/data_extraction_rules.xml
Normal 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
@ -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
|
||||||
|
}
|
BIN
docs/ASR-5515通信协议.docx
Normal file
BIN
docs/iB86.zip
Normal file
21
gradle.properties
Normal 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
@ -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
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal 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
@ -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
@ -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
@ -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'
|