这是我的AIChatActivity.java
package com.example.heart_oxygen_monitoring_system;
import android.os.Bundle;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class AIChatActivity extends AppCompatActivity {
private TextView tvAiResponse;
private RagFlowService ragFlowService;
private int heartRate;
private int spo2;
private int temperature;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ai_chat);
// 初始化视图
tvAiResponse = findViewById(R.id.tvAiResponse);
ragFlowService = new RagFlowService(this);
// 获取传递的健康数据
heartRate = getIntent().getIntExtra("heartRate", 0);
spo2 = getIntent().getIntExtra("spo2", 0);
temperature = getIntent().getIntExtra("temperature", 0);
if (heartRate == 0 && spo2 == 0 && temperature == 0) {
Toast.makeText(this, "未获取到健康数据", Toast.LENGTH_SHORT).show();
finish();
return;
}
// 显示加载状态
tvAiResponse.setText("正在连接AI健康顾问,分析您的健康数据...");
// 开始与AI交互
startAiConversation();
}
private void startAiConversation() {
// 生成健康提示词
String healthPrompt = ragFlowService.generateHealthPrompt(heartRate, spo2, temperature);
// 使用chats_openai端点(你也可以使用agents_openai端点)
String chatId = "41594fc68a0011f08fa396964f1fa6e5"; // 替换为你的实际chat_id
String model = "default-model"; // 可以是任意值,如"gpt-3.5-turbo"
// 使用非流式响应以获得完整回复
ragFlowService.sendChatCompletion(chatId, healthPrompt, model, false, new RagFlowService.ApiCallback() {
@Override
public void onSuccess(String response) {
runOnUiThread(() -> {
String aiResponse = ragFlowService.parseApiResponse(response);
if (aiResponse != null) {
tvAiResponse.setText(aiResponse);
} else {
tvAiResponse.setText("无法解析AI回复,请查看日志获取详细信息");
Toast.makeText(AIChatActivity.this, "解析响应失败", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onError(String error) {
runOnUiThread(() -> {
tvAiResponse.setText("请求失败: " + error);
Toast.makeText(AIChatActivity.this, "AI服务连接失败: " + error, Toast.LENGTH_LONG).show();
});
}
});
}
@Override
public void onBackPressed() {
super.onBackPressed();
}
}
这是我的MainActivity.java
package com.example.heart_oxygen_monitoring_system;
import android.Manifest;
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.UUID;
public class MainActivity extends AppCompatActivity {
// UI组件
private TextView tvConnectionStatus;
private TextView tvHeartRate;
private TextView tvSpO2;
private TextView tvTemperature;
private Button btnScan;
private Button btnOpenRagflow;
// 蓝牙相关
private BluetoothHelper bluetoothHelper;
private BluetoothAdapter bluetoothAdapter;
private BluetoothSocket bluetoothSocket;
private ConnectedThread connectedThread;
private final ArrayList<BluetoothDevice> deviceList = new ArrayList<>();
private BinaryPacketParser packetParser;
// 健康数据
private int currentHeartRate = 0;
private int currentSpO2 = 0;
private int currentTemperature = 0;
// 实时监控
private long lastDataReceivedTime = 0;
private static final long DATA_TIMEOUT_MS = 3000;
private Handler realtimeMonitorHandler = new Handler();
// 权限和请求码
private static final int REQUEST_ENABLE_BT = 1;
private static final int REQUEST_BLUETOOTH_PERMISSIONS = 2;
private static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
private static final String TAG = "MainActivity";
private ActivityResultLauncher<Intent> bluetoothEnableLauncher;
// 二进制数据包解析器回调
private final BinaryPacketParser.PacketListener packetListener = new BinaryPacketParser.PacketListener() {
@Override
public void onSensorDataReceived(int heartRate, int spo2, int temperature) {
runOnUiThread(() -> {
updateHeartRate(String.valueOf(heartRate));
updateOxygenLevel(String.valueOf(spo2));
updateTemperature(String.valueOf(temperature));
});
}
@Override
public void onPacketError(String error) {
Log.w(TAG, "数据包错误: " + error);
runOnUiThread(() -> {
Toast.makeText(MainActivity.this, "数据包错误", Toast.LENGTH_SHORT).show();
});
}
@Override
public void onDebugInfo(String info) {
Log.d(TAG, "调试信息: " + info);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
// 初始化结果监听器
bluetoothEnableLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == RESULT_OK) {
startDeviceDiscovery();
tryAutoConnect();
} else {
Toast.makeText(this, "蓝牙未启用", Toast.LENGTH_SHORT).show();
}
});
initViews();
initBluetooth();
setupButtonListeners();
// 初始化数据包解析器
packetParser = new BinaryPacketParser();
// 如果蓝牙已启用,尝试自动连接
if (bluetoothHelper != null && bluetoothHelper.isBluetoothEnabled() && checkPermissions()) {
tryAutoConnect();
}
}
private void initViews() {
tvConnectionStatus = findViewById(R.id.tvConnectionStatus);
tvHeartRate = findViewById(R.id.tvHeartRate);
tvSpO2 = findViewById(R.id.tvSpO2);
tvTemperature = findViewById(R.id.tvTemperature);
btnScan = findViewById(R.id.btnScan);
btnOpenRagflow = findViewById(R.id.btn_open_ragflow);
updateBluetoothStatus(false);
resetDataDisplay();
}
private void resetDataDisplay() {
tvHeartRate.setText("--");
tvSpO2.setText("--");
tvTemperature.setText("--");
}
private void initBluetooth() {
bluetoothHelper = new BluetoothHelper(this);
bluetoothAdapter = bluetoothHelper.getBluetoothAdapter();
if (!bluetoothHelper.isBluetoothSupported()) {
Toast.makeText(this, "设备不支持蓝牙", Toast.LENGTH_SHORT).show();
return;
}
// 注册广播接收器
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(deviceReceiver, filter);
}
private void setupButtonListeners() {
btnScan.setOnClickListener(v -> {
if (checkPermissions()) {
if (!bluetoothHelper.isBluetoothEnabled()) {
requestEnableBluetooth();
} else {
startDeviceDiscovery();
}
}
});
btnOpenRagflow.setOnClickListener(v -> {
if (currentHeartRate == 0 && currentSpO2 == 0 && currentTemperature == 0) {
Toast.makeText(MainActivity.this, "暂无健康数据,请先连接设备", Toast.LENGTH_SHORT).show();
return;
}
Intent intent = new Intent(MainActivity.this, AIChatActivity.class);
intent.putExtra("heartRate", currentHeartRate);
intent.putExtra("spo2", currentSpO2);
intent.putExtra("temperature", currentTemperature);
startActivity(intent);
});
}
// 获取当前健康数据字符串
public String getCurrentHealthData() {
return String.format("心率: %d bpm, 血氧: %d%%, 体温: %d°C",
currentHeartRate, currentSpO2, currentTemperature);
}
public int getCurrentHeartRate() {
return currentHeartRate;
}
public int getCurrentSpO2() {
return currentSpO2;
}
public int getCurrentTemperature() {
return currentTemperature;
}
private void requestEnableBluetooth() {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
bluetoothEnableLauncher.launch(enableBtIntent);
}
private void startDeviceDiscovery() {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN)
!= PackageManager.PERMISSION_GRANTED) {
return;
}
deviceList.clear();
if (bluetoothAdapter.isDiscovering()) {
bluetoothAdapter.cancelDiscovery();
}
if (bluetoothAdapter.startDiscovery()) {
Toast.makeText(this, "正在搜索JDY-31设备...", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "搜索启动失败", Toast.LENGTH_SHORT).show();
}
}
private final BroadcastReceiver deviceReceiver = new BroadcastReceiver() {
@SuppressLint("MissingPermission")
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (ActivityCompat.checkSelfPermission(MainActivity.this,
Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
return;
}
// 搜索JDY-31设备
if (device != null && device.getName() != null) {
String deviceName = device.getName();
if (deviceName.contains("JDY-31") || deviceName.contains("STM32") || deviceName.contains("HC")) {
if (!deviceList.contains(device)) {
deviceList.add(device);
showDeviceSelectionDialog();
}
}
}
}
}
};
private void showDeviceSelectionDialog() {
if (deviceList.isEmpty()) return;
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("选择蓝牙设备");
ArrayList<String> deviceNames = new ArrayList<>();
for (BluetoothDevice device : deviceList) {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT)
!= PackageManager.PERMISSION_GRANTED) {
continue;
}
deviceNames.add(device.getName() + "\n" + device.getAddress());
}
builder.setItems(deviceNames.toArray(new String[0]), (dialog, which) -> {
BluetoothDevice selectedDevice = deviceList.get(which);
connectToDevice(selectedDevice);
saveDeviceInfo(selectedDevice);
});
builder.setNegativeButton("取消", null);
builder.show();
}
private void saveDeviceInfo(BluetoothDevice device) {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
return;
}
getSharedPreferences("BluetoothPrefs", MODE_PRIVATE)
.edit()
.putString("lastDeviceName", device.getName())
.putString("lastDeviceAddress", device.getAddress())
.apply();
}
@SuppressLint("MissingPermission")
private void tryAutoConnect() {
SharedPreferences prefs = getSharedPreferences("BluetoothPrefs", MODE_PRIVATE);
String lastAddress = prefs.getString("lastDeviceAddress", "");
if (!lastAddress.isEmpty() && bluetoothAdapter != null) {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT)
== PackageManager.PERMISSION_GRANTED) {
try {
BluetoothDevice device = bluetoothAdapter.getRemoteDevice(lastAddress);
if (device != null) {
Toast.makeText(this, "尝试连接上次设备: " + device.getName(), Toast.LENGTH_SHORT).show();
connectToDevice(device);
}
} catch (IllegalArgumentException e) {
Log.e(TAG, "无效的设备地址: " + lastAddress);
}
}
}
}
private void connectToDevice(BluetoothDevice device) {
// 先停止之前的连接
if (connectedThread != null) {
connectedThread.cancel();
connectedThread = null;
}
try {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT)
!= PackageManager.PERMISSION_GRANTED) {
return;
}
bluetoothSocket = device.createRfcommSocketToServiceRecord(MY_UUID);
new Thread(() -> {
try {
// 取消发现以提高连接速度
if (ActivityCompat.checkSelfPermission(MainActivity.this,
Manifest.permission.BLUETOOTH_SCAN) == PackageManager.PERMISSION_GRANTED) {
bluetoothAdapter.cancelDiscovery();
}
bluetoothSocket.connect();
runOnUiThread(() -> {
updateBluetoothStatus(true);
Toast.makeText(MainActivity.this,
"已连接到: " + device.getName(),
Toast.LENGTH_SHORT).show();
});
// 启动数据接收线程
connectedThread = new ConnectedThread(bluetoothSocket);
connectedThread.start();
// 启动实时监控
lastDataReceivedTime = System.currentTimeMillis();
startRealtimeMonitoring();
} catch (IOException e) {
Log.e(TAG, "连接失败", e);
runOnUiThread(() -> {
updateBluetoothStatus(false);
Toast.makeText(MainActivity.this,
"连接失败: " + e.getMessage(),
Toast.LENGTH_SHORT).show();
});
}
}).start();
} catch (IOException e) {
Toast.makeText(this, "创建socket失败", Toast.LENGTH_SHORT).show();
}
}
private class ConnectedThread extends Thread {
private final InputStream mmInStream;
private final BluetoothSocket mmSocket;
private boolean isRunning = true;
public ConnectedThread(BluetoothSocket socket) {
mmSocket = socket;
InputStream tmpIn = null;
try {
tmpIn = socket.getInputStream();
} catch (IOException e) {
}
mmInStream = tmpIn;
}
public void run() {
byte[] buffer = new byte[1024];
int bytesRead;
while (isRunning && !Thread.interrupted()) {
try {
bytesRead = mmInStream.read(buffer);
if (bytesRead > 0) {
// 处理二进制数据
packetParser.processData(buffer, bytesRead, packetListener);
lastDataReceivedTime = System.currentTimeMillis();
}
} catch (IOException e) {
Log.e(TAG, "输入流读取错误", e);
break;
}
}
// 连接断开处理
runOnUiThread(() -> {
updateBluetoothStatus(false);
Toast.makeText(MainActivity.this, "设备连接断开", Toast.LENGTH_SHORT).show();
});
}
public void cancel() {
isRunning = false;
interrupt();
try {
mmSocket.close();
} catch (IOException e) {
Log.e(TAG, "关闭socket失败", e);
}
}
}
private void updateHeartRate(String value) {
tvHeartRate.setText(value);
try {
currentHeartRate = Integer.parseInt(value);
// 心率正常范围:60-100,超出显示红色
if (currentHeartRate < 60 || currentHeartRate > 100) {
tvHeartRate.setTextColor(Color.RED);
} else {
tvHeartRate.setTextColor(ContextCompat.getColor(this, R.color.primary));
}
} catch (NumberFormatException e) {
tvHeartRate.setTextColor(ContextCompat.getColor(this, R.color.primary));
}
}
private void updateOxygenLevel(String value) {
tvSpO2.setText(value);
try {
currentSpO2 = Integer.parseInt(value);
// 血氧正常范围:90-100,低于90显示红色
if (currentSpO2 < 90) {
tvSpO2.setTextColor(Color.RED);
} else {
tvSpO2.setTextColor(ContextCompat.getColor(this, R.color.primary));
}
} catch (NumberFormatException e) {
tvSpO2.setTextColor(ContextCompat.getColor(this, R.color.primary));
}
}
private void updateTemperature(String value) {
tvTemperature.setText(value);
try {
currentTemperature = Integer.parseInt(value);
// 温度异常提示(可选)
if (currentTemperature < 35 || currentTemperature > 38) {
tvTemperature.setTextColor(Color.RED);
} else {
tvTemperature.setTextColor(ContextCompat.getColor(this, R.color.primary));
}
} catch (NumberFormatException e) {
tvTemperature.setTextColor(ContextCompat.getColor(this, R.color.primary));
}
}
private void updateBluetoothStatus(boolean isConnected) {
if (isConnected) {
tvConnectionStatus.setText("蓝牙状态: 已连接");
tvConnectionStatus.setTextColor(ContextCompat.getColor(this, R.color.green));
btnScan.setVisibility(View.GONE);
} else {
tvConnectionStatus.setText("蓝牙状态: 未连接");
tvConnectionStatus.setTextColor(ContextCompat.getColor(this, R.color.red));
btnScan.setVisibility(View.VISIBLE);
resetDataDisplay();
stopRealtimeMonitoring();
}
}
private void startRealtimeMonitoring() {
realtimeMonitorHandler.postDelayed(dataMonitorRunnable, 1000);
}
private void stopRealtimeMonitoring() {
realtimeMonitorHandler.removeCallbacks(dataMonitorRunnable);
}
private Runnable dataMonitorRunnable = new Runnable() {
@Override
public void run() {
long currentTime = System.currentTimeMillis();
if (lastDataReceivedTime > 0 &&
currentTime - lastDataReceivedTime > DATA_TIMEOUT_MS) {
runOnUiThread(() -> {
Toast.makeText(MainActivity.this, "设备无响应", Toast.LENGTH_SHORT).show();
updateBluetoothStatus(false);
});
}
realtimeMonitorHandler.postDelayed(this, 1000);
}
};
private boolean checkPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED ||
ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{
Manifest.permission.BLUETOOTH_CONNECT,
Manifest.permission.BLUETOOTH_SCAN,
Manifest.permission.ACCESS_FINE_LOCATION
}, REQUEST_BLUETOOTH_PERMISSIONS);
return false;
}
} else {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{
Manifest.permission.ACCESS_FINE_LOCATION
}, REQUEST_BLUETOOTH_PERMISSIONS);
return false;
}
}
return true;
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_BLUETOOTH_PERMISSIONS) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
startDeviceDiscovery();
} else {
Toast.makeText(this, "需要权限才能使用蓝牙功能", Toast.LENGTH_SHORT).show();
}
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_ENABLE_BT) {
if (resultCode == RESULT_OK) {
startDeviceDiscovery();
} else {
Toast.makeText(this, "蓝牙未启用", Toast.LENGTH_SHORT).show();
}
}
}
@SuppressLint("MissingPermission")
@Override
protected void onDestroy() {
super.onDestroy();
// 停止实时监控
stopRealtimeMonitoring();
// 取消设备发现
if (bluetoothAdapter != null && bluetoothAdapter.isDiscovering()) {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN)
== PackageManager.PERMISSION_GRANTED) {
bluetoothAdapter.cancelDiscovery();
}
}
// 注销广播接收器
try {
unregisterReceiver(deviceReceiver);
} catch (IllegalArgumentException e) {
Log.e(TAG, "接收器未注册");
}
// 关闭连接
if (connectedThread != null) {
connectedThread.cancel();
}
if (bluetoothSocket != null) {
try {
bluetoothSocket.close();
} catch (IOException e) {
Log.e(TAG, "关闭socket失败", e);
}
}
// 重置解析器
if (packetParser != null) {
packetParser.reset();
}
}
}
这是activity_ai_chat.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
android:background="@android:color/white">
<!-- 标题 -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="🤖 AI健康分析"
android:textSize="24sp"
android:textStyle="bold"
android:textColor="#2196F3"
android:gravity="center"
android:layout_marginBottom="16dp"/>
<!-- 健康数据卡片 -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="12dp"
app:cardElevation="4dp"
app:cardBackgroundColor="#E3F2FD"
android:layout_marginBottom="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="📈 当前健康数据"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="#1976D2"
android:layout_marginBottom="12dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="3">
<!-- 心率 -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="❤️ 心率"
android:textSize="14sp"
android:textColor="#616161"
android:layout_marginBottom="4dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="#D32F2F"
android:text="@{String.valueOf(heartRate)}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="bpm"
android:textSize="12sp"
android:textColor="#9E9E9E"/>
</LinearLayout>
<!-- 分隔线 -->
<View
android:layout_width="1dp"
android:layout_height="40dp"
android:background="#BDBDBD"
android:layout_marginHorizontal="8dp"/>
<!-- 血氧 -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="💨 血氧"
android:textSize="14sp"
android:textColor="#616161"
android:layout_marginBottom="4dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="#1976D2"
android:text="@{String.valueOf(spo2)}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="%"
android:textSize="12sp"
android:textColor="#9E9E9E"/>
</LinearLayout>
<!-- 分隔线 -->
<View
android:layout_width="1dp"
android:layout_height="40dp"
android:background="#BDBDBD"
android:layout_marginHorizontal="8dp"/>
<!-- 体温 -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="🌡️ 体温"
android:textSize="14sp"
android:textColor="#616161"
android:layout_marginBottom="4dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="#FFA000"
android:text="@{String.valueOf(temperature)}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="°C"
android:textSize="12sp"
android:textColor="#9E9E9E"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- AI回复区域 -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:cardCornerRadius="12dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="💡 AI健康建议"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="#455A64"
android:layout_marginBottom="12dp"/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tvAiResponse"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="16sp"
android:textColor="#37474F"
android:lineSpacingMultiplier="1.4"
android:text="正在分析您的健康数据,请稍候...\n\n这将需要几秒钟时间..."/>
</ScrollView>
</LinearLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>
这是activity_main.xml
<?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"
android:background="@color/light_background"
tools:context=".MainActivity">
<!-- 标题 -->
<TextView
android:id="@+id/titleText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="智能健康监测系统"
android:textAlignment="center"
android:textSize="24sp"
android:textStyle="bold"
android:textColor="@color/primary_dark"
android:layout_marginBottom="24dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<!-- 实时数据卡片 -->
<androidx.cardview.widget.CardView
android:id="@+id/dataCard"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
app:cardCornerRadius="16dp"
app:cardElevation="8dp"
app:cardBackgroundColor="@color/white"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/titleText">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="实时健康数据"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="@color/primary_dark"
android:layout_marginBottom="16dp"
android:drawablePadding="8dp"
/>
<!-- 心率数据行 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:orientation="horizontal"
android:gravity="center_vertical">
<ImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginEnd="12dp"
android:src="@drawable/ic_heart_rate"
app:tint="@color/heart_rate"/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="心率"
android:textSize="14sp"
android:textColor="@color/text_secondary"/>
<TextView
android:id="@+id/tvHeartRate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="--"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="@color/primary_dark"/>
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="bpm"
android:textSize="16sp"
android:textColor="@color/text_secondary"/>
</LinearLayout>
<!-- 分隔线 -->
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/divider"
android:layout_marginBottom="16dp"/>
<!-- 血氧数据行 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:orientation="horizontal"
android:gravity="center_vertical">
<ImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginEnd="12dp"
android:src="@drawable/ic_oxygen"
app:tint="@color/oxygen"/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="血氧饱和度"
android:textSize="14sp"
android:textColor="@color/text_secondary"/>
<TextView
android:id="@+id/tvSpO2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="--"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="@color/primary_dark"/>
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="%"
android:textSize="16sp"
android:textColor="@color/text_secondary"/>
</LinearLayout>
<!-- 分隔线 -->
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/divider"
android:layout_marginBottom="16dp"/>
<!-- 温度数据行 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<ImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginEnd="12dp"
android:src="@drawable/ic_temperature"
app:tint="@color/temperature"/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="体温"
android:textSize="14sp"
android:textColor="@color/text_secondary"/>
<TextView
android:id="@+id/tvTemperature"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="--"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="@color/primary_dark"/>
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="°C"
android:textSize="16sp"
android:textColor="@color/text_secondary"/>
</LinearLayout>
<!-- 分隔线 -->
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/divider"
android:layout_marginBottom="16dp"/>
<!-- 卡路里数据行 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:orientation="horizontal"
android:gravity="center_vertical">
<ImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginEnd="12dp"
android:src="@drawable/ic_kcal"
app:tint="@color/red"/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="卡路里消耗"
android:textSize="14sp"
android:textColor="@color/text_secondary"/>
<TextView
android:id="@+id/tvCalories"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="--"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="@color/primary_dark"/>
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="kcal"
android:textSize="16sp"
android:textColor="@color/text_secondary"/>
</LinearLayout>
<!-- 分隔线 -->
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/divider"
android:layout_marginBottom="16dp"/>
<!-- 疾病预警风险数据行 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:orientation="horizontal"
android:gravity="center_vertical">
<ImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginEnd="12dp"
android:src="@drawable/ic_disease"/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="疾病预警风险"
android:textSize="14sp"
android:textColor="@color/text_secondary"/>
<TextView
android:id="@+id/tvDiseaseRisk"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="--"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="@color/primary_dark"/>
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textSize="16sp"
android:textColor="@color/text_secondary"/>
</LinearLayout>
<!-- 分隔线 -->
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/divider"
android:layout_marginBottom="16dp"/>
<!-- 高海拔适应能力数据行 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<ImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginEnd="12dp"
android:src="@drawable/ic_mountain" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="高海拔适应能力"
android:textSize="14sp"
android:textColor="@color/text_secondary"/>
<TextView
android:id="@+id/tvAltitudeAdaptation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="--"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="@color/primary_dark"/>
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textSize="16sp"
android:textColor="@color/text_secondary"/>
</LinearLayout>
<!-- 分隔线 -->
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/divider"
android:layout_marginBottom="16dp"/>
<!-- 步数数据行 -->
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- 扫描设备按钮 (单独放在ConstraintLayout中) -->
<androidx.cardview.widget.CardView
android:id="@+id/scanCard"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
app:cardCornerRadius="12dp"
app:cardElevation="4dp"
app:layout_constraintEnd_toStartOf="@+id/aiCard"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dataCard">
<Button
android:id="@+id/btnScan"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="扫描设备"
android:textAllCaps="false"
android:backgroundTint="@color/primary"
android:textColor="@android:color/black"
android:drawablePadding="8dp"
app:drawableStartCompat="@drawable/ic_bluetooth"
style="@style/Widget.AppCompat.Button.Borderless"/>
</androidx.cardview.widget.CardView>
<!-- AI助手按钮 (单独放在ConstraintLayout中) -->
<androidx.cardview.widget.CardView
android:id="@+id/aiCard"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
app:cardCornerRadius="12dp"
app:cardElevation="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/scanCard"
app:layout_constraintTop_toBottomOf="@id/dataCard">
<Button
android:id="@+id/btn_open_ragflow"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="AI健康分析"
android:textAllCaps="false"
android:backgroundTint="@color/accent"
android:textColor="@android:color/black"
android:drawablePadding="8dp"
app:drawableStartCompat="@drawable/ic_ai"
style="@style/Widget.AppCompat.Button.Borderless"/>
</androidx.cardview.widget.CardView>
<!-- 连接状态卡片 -->
<androidx.cardview.widget.CardView
android:layout_width="0dp"
android:layout_height="wrap_content"
app:cardCornerRadius="12dp"
app:cardElevation="2dp"
app:cardBackgroundColor="@color/status_background"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/scanCard"
app:layout_constraintBottom_toBottomOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
android:gravity="center_vertical">
<ImageView
android:id="@+id/ivConnectionStatus"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="12dp"
android:src="@drawable/ic_bluetooth"
app:tint="@color/red"/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="设备连接状态"
android:textSize="14sp"
android:textColor="@color/text_secondary"/>
<TextView
android:id="@+id/tvConnectionStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="未连接设备"
android:textSize="16sp"
android:textStyle="bold"
android:textColor="@color/red"/>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>
这是我的RagflowService.java
// RagFlowService.java
package com.example.heart_oxygen_monitoring_system;
import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;
import com.google.gson.Gson;
import java.io.IOException;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
public class RagFlowService {
private static final String TAG = "RagFlowService";
private static final String BASE_URL = "http://192.168.31.105:8880/api/v1/";
private static final String API_KEY = "ragflow-QyYjVlNTc2ODhjMzExZjBhYTRlZGExOT"; // 替换为你的实际API Key
private static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
private final OkHttpClient client;
private final Gson gson;
private final Context context;
public RagFlowService(Context context) {
this.context = context;
this.client = new OkHttpClient();
this.gson = new Gson();
}
public interface ApiCallback {
void onSuccess(String response);
void onError(String error);
}
// 发送聊天消息(chats_openai端点)
public void sendChatCompletion(String chatId, String message, String model, boolean stream, ApiCallback callback) {
sendCompletion("chats_openai", chatId, message, model, stream, callback);
}
// 发送代理消息(agents_openai端点)
public void sendAgentCompletion(String agentId, String message, String model, boolean stream, ApiCallback callback) {
sendCompletion("agents_openai", agentId, message, model, stream, callback);
}
private void sendCompletion(String endpoint, String id, String message, String model, boolean stream, ApiCallback callback) {
OpenAIApiRequest.Message[] messages = {new OpenAIApiRequest.Message("user", message)};
OpenAIApiRequest request = new OpenAIApiRequest(model, messages, stream);
String json = gson.toJson(request);
new CompletionTask(endpoint, id, callback).execute(json);
}
private class CompletionTask extends AsyncTask<String, Void, String> {
private final String endpoint;
private final String id;
private final ApiCallback callback;
private String error = null;
public CompletionTask(String endpoint, String id, ApiCallback callback) {
this.endpoint = endpoint;
this.id = id;
this.callback = callback;
}
@Override
protected String doInBackground(String... params) {
String jsonBody = params[0];
try {
String url = BASE_URL + endpoint + "/" + id + "/chat/completions";
RequestBody body = RequestBody.create(jsonBody, JSON);
Request request = new Request.Builder()
.url(url)
.post(body)
.addHeader("Content-Type", "application/json")
.addHeader("Authorization", "Bearer " + API_KEY)
.build();
Log.d(TAG, "Sending request to: " + url);
Log.d(TAG, "Request body: " + jsonBody);
Response response = client.newCall(request).execute();
String responseBody = response.body().string();
Log.d(TAG, "Response code: " + response.code());
Log.d(TAG, "Response body: " + responseBody);
if (response.isSuccessful()) {
return responseBody;
} else {
try {
ErrorResponse errorResponse = gson.fromJson(responseBody, ErrorResponse.class);
error = "错误 " + errorResponse.getCode() + ": " + errorResponse.getMessage();
} catch (Exception e) {
error = "API请求失败: " + response.code() + " - " + response.message();
}
return null;
}
} catch (IOException e) {
error = "网络错误: " + e.getMessage();
return null;
}
}
@Override
protected void onPostExecute(String result) {
if (result != null) {
callback.onSuccess(result);
} else {
callback.onError(error != null ? error : "未知错误");
}
}
}
// 生成健康提示词
public String generateHealthPrompt(int heartRate, int spo2, int temperature) {
StringBuilder prompt = new StringBuilder();
prompt.append("请作为专业医疗健康顾问,分析以下实时健康监测数据:\n\n");
prompt.append("📊 当前健康数据:\n");
prompt.append("• 心率: ").append(heartRate).append(" bpm\n");
prompt.append("• 血氧饱和度: ").append(spo2).append("%\n");
prompt.append("• 体温: ").append(temperature).append("°C\n\n");
prompt.append("请根据医学标准提供专业分析:\n");
prompt.append("1. 各项指标是否在正常范围内(请给出具体数值参考)\n");
prompt.append("2. 潜在的健康风险和需要注意的症状\n");
prompt.append("3. 具体的健康改善建议和生活习惯调整\n");
prompt.append("4. 是否需要立即就医或进一步检查的建议\n\n");
prompt.append("请用专业但通俗易懂的语言回复,面向普通用户。");
return prompt.toString();
}
// 解析API响应
public String parseApiResponse(String jsonResponse) {
try {
OpenAIApiResponse response = gson.fromJson(jsonResponse, OpenAIApiResponse.class);
if (response != null && response.getChoices() != null && response.getChoices().length > 0) {
return response.getChoices()[0].getMessage().getContent();
}
} catch (Exception e) {
Log.e(TAG, "解析API响应错误", e);
}
return null;
}
}
我要实现心率血氧体温在stm32测出来后通过蓝牙传输给APP实时显示已经成功,我现在使用ragflow,docker和ollama部署了本地AI,需求是当用户点击AI健康分析按钮,自动将实时显示的心率血氧和体温发送给AI不需要我们人工发送消息给AI,然后AI回复健康建议到一个界面,然后用户还能跟AI继续沟通
最新发布