1. 简述
由于实验测试,需要matlab蓝牙连接手机并实时接收处理手机传感器数据。
因此,网上抄抄补补,代码写的很是简陋,仍旧有很多需要补足的地方,如:手机端代码都写在了主活动里;用词排版等不够规范;matlab检测是否传输暂停是有问题的; matlab并行操作并没有进行测试,不清楚是否会数据堵塞或后续利用传感器数据出错等等。
但还在还是能达到matlab蓝牙实时接收存储传感器数据的目的。同时该代码还是很容易阅读理解,发出来供大家参考。
AS版本为:Android Studio Hedgehog | 2023.1.1 Patch 2
Java JDK 版本为:java version "21.0.2" 2024-01-16 LTS
gradle版本为:gradle-8.2-bin.zip
matlab版为:R2022a
2. AS代码
2.1 AndroidManifest
关于蓝牙的权限配置,没有用动态申请(懒得整了),直接全部都给他获取,然后没有的反正就手机操作给他打开嘛。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- 蓝牙权限配置-->
<!-- 普通权限-->
<uses-permission android:name="android.permission.BLUETOOTH"/>
<!-- 高级权限-->
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<!--存储权限-->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@drawable/ic_launcher_background"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Test_BLE3"
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>
2.2 MainActivity
注释我个人觉得还是写的很清楚,就不多说啥了哈,反正都在这一个活动里。
package com.example.test_ble3;
import androidx.appcompat.app.AppCompatActivity;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.Intent;
import android.os.Bundle;
import android.bluetooth.BluetoothAdapter;
import android.os.Handler;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import java.lang.*;
public class MainActivity extends AppCompatActivity {
/* ***********蓝牙连接相关*****************/
public BluetoothAdapter mBluetoothAdapter = null;
AcceptThread acceptThread;//服务端线程
ConnectedThread connectedThread;//管理连接线程
EditText et_dataTest;
Button bt_server;
Button bt_cancel;
Button bt_dataTrans;
TextView tv_bind;
TextView tv_connStatus;
StringBuilder dataString_Acc = null;//加计数据
StringBuilder dataString_Gyro = null;//陀螺数据
StringBuilder dataString_Magnetic = null;//磁力计数据
StringBuilder dataString = null;//传感器数据
boolean is_bind = false;//蓝牙绑定开关
boolean is_TransStop = true;//蓝牙取消传输开关
boolean is_ObtainStop = true;//数据暂停获取开关
/* **************传感器获取相关****************/
TextView tv_Gyro;
TextView tv_Magnetic;
TextView tv_LinearAcc;
Button bt_dataStop;
Timer timer = null;
TimerTask timerTask;
private SensorManager mSensorManager;
private final Handler handler = new Handler();
//获取传感器数据dataString的副本以传递给用于数据传输的子线程
private synchronized String getDataStringCopy() {
return dataString != null ? dataString.toString() : "";
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//*蓝牙连接*///
//连接状态显示
tv_connStatus = findViewById(R.id.Tv_connStatus);
//构造本地蓝牙适配器实例
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
//开启服务端按钮实例化
bt_server = (Button) findViewById(R.id.Bt_server);
//点击事件
bt_server.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//开启蓝牙,可被其他设备监听
Intent discoverIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 1500);
try {
startActivity(discoverIntent);
} catch (SecurityException ignored) {
}
// 开启服务器端
acceptThread = new AcceptThread();
acceptThread.start();
}
});
//断开蓝牙连接实例化
bt_cancel = findViewById(R.id.Bt_cancel);
//点击事件
bt_cancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
acceptThread.cancel();
}
});
/传感器数据采集传输//
//实例化
tv_Gyro = (TextView) findViewById(R.id.Tv_Gyro);
tv_LinearAcc = (TextView) findViewById(R.id.Tv_LinearAcc);
tv_Magnetic = (TextView) findViewById(R.id.TV_Magnetic);
// et_dataTest = findViewById(R.id.ET_DataTest);
tv_bind = findViewById(R.id.Tv_bind);
bt_dataTrans = findViewById(R.id.Bt_dataTrans);
//获取传感器管理者
mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
//传感器数据传输
bt_dataTrans.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
tv_bind.setText("正在采集传输数据");
//启动传感数据采集(注册三个传感器)
StartSensorListening();
//开启数据传输
SensorTransThread();
}
});
//采集传输暂停
bt_dataStop = findViewById(R.id.Bt_dataStop);
bt_dataStop.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
StopSensorListening();//停止传感器采集
StopTrans();
tv_bind.setText("蓝牙已绑定,数据传输已暂停");
}
});
}
/**
* *********** end of onCreate ************
**/
//****蓝牙连接部分*****///
//蓝牙作为服务端的类的线程
public class AcceptThread extends Thread {
private final BluetoothServerSocket mmServerSocket;//服务端接口
private final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
private static final String DEVICE_NAME = "testBLE3";
// private BluetoothAdapter bluetoothAdapter;
public AcceptThread() {
// Use a temporary object that is later assigned to mmServerSocket,
// because mmServerSocket is final
// mBluetoothAdapter = adapter;
// mHandler = handler;
BluetoothServerSocket tmp = null;
try {
tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(DEVICE_NAME, MY_UUID);
} catch (IOException | SecurityException e) {
e.printStackTrace();
}
mmServerSocket = tmp;
//
// 此处注释掉的除开这句以外,private AcceptThread()应改为private AcceptThread(BluetoothAdapter bluetoothAdapter)
// AcceptThread.this.bluetoothAdapter = bluetoothAdapter;
tv_connStatus.setText(getString(R.string.txtText1));
}
public void run() {
BluetoothSocket socket = null;
// Keep listening until exception occurs or a socket is returned
//服务器端持续监听,直到出现异常或套接字
while (true) {
try {
socket = mmServerSocket.accept();
tv_connStatus.setText(getString(R.string.txtText2));
is_bind = true;
tv_bind.setText("蓝牙已绑定,等待传输数据");
} catch (IOException e) {
break;
}
// If a connection was accepted 如果接受了一个连接
if (socket != null) {
// Do work to manage the connection (in a separate thread)
//单线程中管理连接
manageConnectedSocket(socket);
//关闭搜索连接
try {
mmServerSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
break;
}
}
}
public void cancel() {
try {
mmServerSocket.close();
is_bind = false;
} catch (IOException ignored) {
}
}
//新开一个线程管理连接
public void manageConnectedSocket(BluetoothSocket socket) {
connectedThread = new ConnectedThread(socket);
connectedThread.start();
}
}
/********* end of AcceptThread ******/
//单独一个线程管理连接的类
public class ConnectedThread extends Thread {
private final BluetoothSocket mmsocket;
private final InputStream mmInStream;
private final OutputStream mmOutStream;
//输入和输出流
public ConnectedThread(BluetoothSocket mmsocket) {
this.mmsocket = mmsocket;
InputStream tmpIn = null;
OutputStream tmpOut = null;
// Get the BluetoothSocket input and output streams
try {
tmpIn = mmsocket.getInputStream();
tmpOut = mmsocket.getOutputStream();
} catch (IOException ignored) {
}
mmInStream = tmpIn;
mmOutStream = tmpOut;
}
public void run() {
//流的缓冲存储
byte[] buffer = new byte[10000];
int bytes;
while (true) {
try {
bytes = mmInStream.read(buffer);
handler.obtainMessage(0, bytes, -1, buffer).sendToTarget();
} catch (IOException e) {
e.printStackTrace();
break;
}
}
}
public void write(byte[] bytes) {
try {
mmOutStream.write(bytes);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/********* end of ConnectedThread ******/
///****传感器数据采集*****
//启动传感器数据采集 注册传感器
private SensorEventListener listener = new SensorEventListener() {
@Override
public void onAccuracyChanged(Sensor sensor, int i) {
}
public void onSensorChanged(SensorEvent e) {
StringBuilder sb = null;
StringBuilder data = new StringBuilder();
long timeStamp = System.currentTimeMillis();
String timestampString = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(timeStamp));
switch (e.sensor.getType()) {
// TODO: 2024/3/14 e.value就是传感器的三轴数据流,
// 理论上从这里就可以传入matlab了.
case Sensor.TYPE_GYROSCOPE: //陀螺
sb = new StringBuilder();
sb.append(e.values[0])
.append(" ").append(e.values[1])
.append(" ").append(e.values[2]);
dataString_Gyro = sb;
tv_Gyro.setText(sb.toString());
break;
case Sensor.TYPE_ACCELEROMETER: //加计
sb = new StringBuilder();
sb.append(e.values[0])
.append(" ").append(e.values[1])
.append(" ").append(e.values[2]);
dataString_Acc = sb;
tv_LinearAcc.setText(dataString_Acc.toString());
break;
case Sensor.TYPE_PRESSURE: //压力计
sb = new StringBuilder();
sb.append(e.values[0]);
dataString_Magnetic = sb;
tv_Magnetic.setText(sb.toString());
break;
}
//陀螺、加计、磁力计的数据汇总
// 有时间的string
// data.append(timestampString).append(" ").append(dataString_Gyro)
// .append(" ").append(dataString_Acc)
// .append(" ").append(dataString_Magnetic).append("\n");
data.append(dataString_Gyro)
.append(" ").append(dataString_Acc)
.append(" ").append(dataString_Magnetic).append("\n");
//用于数据传输的存储dataString
dataString = data;
}
};
public void StartSensorListening() {
//陀螺传感器注册监听器
mSensorManager.registerListener(listener, mSensorManager.getDefaultSensor(
Sensor.TYPE_GYROSCOPE), SensorManager.SENSOR_DELAY_FASTEST);
//磁场传感器注册监听器
mSensorManager.registerListener(listener, mSensorManager.getDefaultSensor(
Sensor.TYPE_PRESSURE), SensorManager.SENSOR_DELAY_FASTEST);
//加速度传感器注册监听器
mSensorManager.registerListener(listener, mSensorManager.getDefaultSensor(
Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_FASTEST);
}
public void StopSensorListening() {
mSensorManager.unregisterListener(listener);
}
///******传感器数据传输*****///
//开始传输
private void SensorTransThread() {
if (timer == null) {
timer = new Timer();
}
timerTask = new TimerTask() {
@Override
public void run() {
String dataStringCopy = getDataStringCopy();
connectedThread.write(dataStringCopy.getBytes());
}
};
new Thread(new Runnable() {
@Override
public void run() {
timer.schedule(timerTask, 0, 20);//延迟为0,间隔为20ms,采样率为50HZ
// timer.schedule(new TimerTask() {
// @Override
// public void run() {
// String dataStringCopy =getDataStringCopy();
// connectedThread.write(dataStringCopy.getBytes());
// }
// },0,20);//延迟为0,间隔为20ms,采样率为50HZ
//
}
}).start();
}
//停止传输
private void StopTrans() {
// is_TransStop = false;
if (timer != null) {
timer.cancel();
timer = null;
}
}
}
2.3 activity_main
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
tools:context=".MainActivity">
<!--显示连接的状态-->
<TextView
android:id="@+id/Tv_connStatus"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<!--开启服务端模式按钮-->
<Button
android:id="@+id/Bt_server"
android:text="@string/服务端模式"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<!--断开连接按钮-->
<Button
android:id="@+id/Bt_cancel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/断开连接" />
<!--传感器数据显示-->
<!--陀螺、加计、磁力计-->
<TextView
android:id="@+id/Tv_Gyro"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/Tv_LinearAcc"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/TV_Magnetic"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<!--蓝牙是否绑定及是否在传输数据-->
<Button
android:id="@+id/Bt_dataTrans"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/开始数据传输" />
<!--关闭数据传输-->
<Button
android:id="@+id/Bt_dataStop"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/关闭数据传输" />
<TextView
android:id="@+id/Tv_bind"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
tools:ignore="MissingConstraints" />
<!-- <!–传输的数据显示–>-->
<!-- <EditText-->
<!-- android:id="@+id/ET_DataTest"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="match_parent"-->
<!-- tools:ignore="MissingConstraints"-->
<!-- />-->
</LinearLayout>
3. matlab代码
matlab方面还需要下载一个软件支持啥的,获取附加功能里下就行了。
是这俩其中之一好像,都下上,都一样!
matlab我写成了一个函数,具体如下:
ps:凭什么matlab在csdn代码引用里在最后一个,瞧不起我大matlab是吧!
function [dataSensor] = BLE_DataTrans(PhoneName)
% 利用蓝牙实时传输手机传感器数据
% 输入参数:
% PhoneName:手机蓝牙的名称
% 输出参数:
% dataSensor:传感器数据数组 陀螺、加计、压力计
%% 并行设置
% 检查是否加载Parallel Computing Toolbox
if ~isempty(ver('parallel'))
disp("Parallel Computing Toolbox 已加载");
else
error("Parallel Computing Toolbox 未安装或加载");
end
%% 蓝牙连接
% PhoneName = "2857747045的Mi 10S";
BLE_List = bluetoothlist;
[Ble_List_Rows,BLE_Lise_Cols]=size(BLE_List);
for i = 1:Ble_List_Rows
switch BLE_List{i,"Status"}
case "准备连接"
%%将categorical类转为整形数
BLE_channel1 =str2double(char( BLE_List{i,"Channel"}));
% device = bluetooth("HuaweiP50",BLE_channel1);
device = bluetooth(PhoneName,BLE_channel1);
disp("蓝牙连接成功,等待数据传输");
disp(device);
break;
case "未知"
disp("设备状态为:未知。请重新调试蓝牙设备");
break;
case "不支持"
disp("设备状态为:不支持。请重新调试蓝牙设备");
break;
case "需要配对"
disp("设备状态为:等待配对。请将手机与电脑蓝牙先进行配对");
break;
case "已连接"
disp("设备状态为:已连接。请清除matlab连接记录再重新尝试连接");
break;
end
end
%% 数据传输
dataSensor = [];
timeout = 2 ;%设置超时时间,单位为秒
lastDataTime = tic;%初始化上一次读取数据的时间
if ~isempty(device)
disp("蓝牙设备已连接");
% device.Timeout = 5;%设置超时阈值时间为1s.
while true
data =readline(device);%读取到的数据为 陀螺、加计、压力计
try
if ~isempty(data)
%读取的新元素
dataElements = str2double(strsplit(data,' '));
%将新元素存储到数组里
dataSensor = [dataSensor;dataElements];
%接收到新数据,重置计时器
lastDataTime = tic;
disp(data);
end
catch ME
%检查是否超时
elapsedTime = toc(lastDataTime);%存储自上次读取数据以来经过的时间
if elapsedTime > timeout
disp("传输已被暂停");
break;
end
end
end
end
end
将上述函数改为程序就是把输入参数phonename自己设个值就行了,同时,上面的关于检查传输是否暂停的部分是有问题的,会误判,但精力不够(Orz),就没继续改下去了。
4. 操作流程
手机软件名称为 test_BLE3
操作界面如图所示:
具体操作步骤为:
1.点击"服务端模式",电脑与手机蓝牙配对;
2.matlab运行程序自动进行蓝牙连接(matlab搜索蓝牙设备需要大概30-35s时间);
3.成功连接后点击"开始数据传输"进行数据传输,示例里传输的传感器数据为:陀螺、加计、压力计;
4.点击"关闭数据传输"可以暂停手机端的数据传输。