matlab蓝牙连接手机并实时传输存储手机传感器数据

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" />

<!--    &lt;!&ndash;传输的数据显示&ndash;&gt;-->
<!--    <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.点击"关闭数据传输"可以暂停手机端的数据传输。

  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

园度

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值