[Android] PacketParser 二进制协议转换工具

本文介绍了如何在Android中使用@ParsePacket注解来标记实体类,自动生成PacketParser解析类。通过添加该注解,可以便捷地进行二进制协议转换,简化开发流程。在gradle中配置相关依赖即可开始使用。
这个工具通过自动生成解析类,实现了字节数组和对象之间的转换。

使用@ParsePacket注解标注实体类:

@ParsePacket(
        "header:1|cmd:2|len:2|seq:2|mac:6|data:this.len-6|check:1|tail:1"
)
public class TargetObject {
    public byte header;
    public short cmd;
    public short len;
    public short seq;
    public byte[] mac;
    public byte[] data;
    public byte check;
    public byte tail;
}

框架自动生成解析类 <类名>PacketParser

public class TargetObjectPacketParser {
    public static final TargetObject parse(byte[] src) {
        return parse(src, new TargetObject());
    }

    public static final TargetObject parse(byte[] bytes, TargetObject src) {
        ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
        src.header = byteBuffer.get();
        src.cmd = byteBuffer.getShort();
        src.len = byteBuffer.getShort();
        src.seq = byteBuffer.getShort();
        src.mac = new byte[6];
        byteBuffer.get(src.mac);
        src.data = new byte[src.len-6];
        byteBuffer.get(src.data);
        src.check = byteBuffer.get();
        src.tail = byteBuffer.get();
        return src;
    }

    public static final byte[] toBytes(TargetObject src) {
        ByteBuffer byteBuffer = ByteBuffer.allocate(1 + 2 + 2 + 2 + 6 + src.len-6 + 1 + 1);
        byteBuffer.put(src.header);
        byteBuffer.putShort(src.cmd);
        byteBuffer.putShort(src.len);
        byteBuffer.putShort(src.seq);
        byteBuffer.put(src.mac);
        byteBuffer.put(src.data);
        byteBuffer.put(src.check);
        byteBuffer.put(src.tail);
        return byteBuffer.array();
    }
}

使用方法:

String data = "AA11220008556677889911223344556677";
byte[] bytes = hexToBytes(data);
// bytes to object
TargetObject targetObject = TargetObjectPacketParser.parse(bytes); // suffix with "PacketParser"
//object to bytes
byte[] toBytes = TargetObjectPacketParser.toBytes(targetObject);

gradle

compile 'com.legendmohe.maven:packetparser-annonation:0.1'
apt 'com.legendmohe.maven:packetparser-complier:0.1'


查看原文:http://legendmohe.net/2016/08/26/android-packetparser-%e4%ba%8c%e8%bf%9b%e5%88%b6%e5%8d%8f%e8%ae%ae%e8%bd%ac%e6%8d%a2%e5%b7%a5%e5%85%b7/
这是我的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继续沟通
最新发布
09-11
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值