Android Studio制作手机App:通过手机蓝牙(Bluetooth)与STM32上的低功耗蓝牙(HC-42)连接通信,实现手机端对单片机的控制。

news2024/11/25 10:36:31

背景:

本文的内容是针对单片机蓝牙模块(HC-42)开发的手机App。在这之前,我想先声明一点,手机与手机间的蓝牙连接方式”与“手机与HC间的蓝牙连接方式”是不一样的。原因就是手机搭配的是“经典蓝牙”模块,HC等蓝牙属于“低功耗蓝牙”模块。(二者的区别想了解的话建议你去看看其他朋友的文章),我在这里只想简单说一下这二者在功能代码实现上可以说是完全不一样的。这就解释了有一些朋友制作的软件明明可以与手机,平板等设备配对连接,却一直与HC蓝牙配对失败。

前言:

本文的内容只讲如何实现手机与HC蓝牙的配对,如果想了解一下手机与手机,手机与平板间的“经典蓝牙”通信方式,可以看我往期的博文,这篇博文讲的是如何制作一个基于蓝牙通信的聊天软件(类似于微信功能),也是一个挺有意思的项目(Android Studio制作蓝牙聊天通讯软件)

本文内容简介:

制作一个手机APP,无线连接HC蓝牙模块,将手机端数据发送给HC,从而控制STM32,文末会有资源分享。

简单通讯原理图:

如何制作这样一个App?

首先看一下本次的效果图:

 一、软件UI界面部分的设计实现

可以看 软件UI界面设计的实现 这篇博文,先实现界面设计后,再完成接下来的功能代码实现。

二、功能代码的实现。

1、在AndroidManifest.xml中添加依赖:

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"

 2、新建文件BlueToothController .java,完整代码及其解析如下:

package BluetoothPackage;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.content.Context;
import android.content.Intent;
import java.util.ArrayList;
import java.util.List;
/**
 * Created by WYB on 2023/4/28.
 */
public class BluetoothController {
    private BluetoothAdapter mAdapter;//本手机的蓝牙适配器
    private BluetoothLeScanner mLeScanner;//本手机蓝牙适配器上的扫描硬件
    private Activity mActivity;
    public static final int REQUEST_CODE_ENABLE_BLUETOOTH = 0;

    public BluetoothController(Activity activity){
        mAdapter = BluetoothAdapter.getDefaultAdapter();//获取本机蓝牙适配器
        mLeScanner = mAdapter.getBluetoothLeScanner();//获取本机蓝牙扫描器
        mActivity = activity;
    }

    public BluetoothAdapter getAdapter() {
        return mAdapter;
    }
    public BluetoothLeScanner getmLeScanner(){
        return mLeScanner;
    }
    /*
        打开蓝牙设备
    */
    public void  turnOnBlueTooth(){
        Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        mActivity.startActivityForResult(intent,REQUEST_CODE_ENABLE_BLUETOOTH);
    }
    /*
        关闭蓝牙设备
    */
    public void  turnOffBlueTooth(){
        mAdapter.disable();
    }
    /**
     * 打开蓝牙可见性,让别的设备发现我
     */
    public void enableVisibily(Context context){
        Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
        intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION,300);
        context.startActivity(intent);
    }
    /*
        查找未绑定的蓝牙设备
    */
    public void findDevice(){
        assert (mAdapter!=null);
        mAdapter.startDiscovery();
    }
    /*
        查看已绑定的蓝牙设备
    */
    public List<BluetoothDevice> getBondedDeviceList(){
        return new ArrayList<>(mAdapter.getBondedDevices());
    }

    /*
        开启扫描
    */
    public void scanBlueTooth(ScanCallback scanCallback){
        mLeScanner.startScan(scanCallback);
    }
    /*
        关闭扫描
    */
    public void stopBlueTooth(ScanCallback scanCallback){
        mLeScanner.stopScan(scanCallback);
    }
    /*
        连接设备
    */
    public BluetoothGatt connectBlueTooth(BluetoothDevice bluetoothDevice, boolean autoConnect, BluetoothGattCallback gattCallback){
        return bluetoothDevice.connectGatt(this.mActivity,autoConnect,gattCallback);
    }
}

3、MainActivity .java,完整代码及其解析如下:

package com.example.wyb.bluetoothchatui;
import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanResult;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Handler;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.ListView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
import BluetoothPackage.BluetoothController;
import MyClass.DeviceAdapter;
import MyClass.DeviceClass;
public class MainActivity extends AppCompatActivity {
    private DeviceAdapter mAdapter1,mAdapter2;
    private List<DeviceClass> mbondDeviceList = new ArrayList<>();//搜索到的所有已绑定设备保存为列表(仅保留名称与地址)
    private List<DeviceClass> mfindDeviceList = new ArrayList<>();//搜索到的所有未绑定设备保存为列表(仅保留名称与地址)
    private List<BluetoothDevice> bondDevices = new ArrayList<>();//搜索到的所有的已绑定设备
    private List<BluetoothDevice> findDevices = new ArrayList<>();//搜索到的所有的未绑定设备
    private BluetoothController mbluetoothController;
    private Toast mToast;
    private mScanCallBack myScanCallBack;
    private static final int PERMISSION_REQUEST_COARSE_LOCATION=1;
    private Button findBtn;
    private Handler mHandler = new Handler();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mbluetoothController = new BluetoothController(this);
        Init_listView();//初始化设备列表
        findBtn = (Button) findViewById(R.id.button1);
        Init_Bluetooth();//开启蓝牙相关权限
    }
    //扫描蓝牙功能的实现
    private class mScanCallBack extends ScanCallback{
        @Override
        public void onScanResult(int callbackType, ScanResult result){
            super.onScanResult(callbackType,result);
            if(!findDevices.contains(result.getDevice()))
            {
                mfindDeviceList.add(new DeviceClass(result.getDevice().getName(),result.getDevice().getAddress()));
                findDevices.add(result.getDevice());
                mAdapter2.notifyDataSetChanged();
            }
        }
        @Override
        public void onBatchScanResults(List<ScanResult> results){
            super.onBatchScanResults(results);
        }
        @Override
        public void onScanFailed(int errorCode){
            super.onScanFailed(errorCode);
        }
    }
    //初始化蓝牙权限
    private void Init_Bluetooth(){
        myScanCallBack = new mScanCallBack();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (this.checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
                requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, PERMISSION_REQUEST_COARSE_LOCATION);
            }
        }
        mbluetoothController.turnOnBlueTooth();
    }
    //初始化列表,适配器的加载
    public void Init_listView(){
        mAdapter1 = new DeviceAdapter(MainActivity.this, R.layout.device_item, mbondDeviceList);
        ListView listView1 = (ListView)findViewById(R.id.listview1);
        listView1.setAdapter(mAdapter1);
        mAdapter1.notifyDataSetChanged();
        mAdapter2 = new DeviceAdapter(MainActivity.this, R.layout.device_item, mfindDeviceList);
        ListView listView2 = (ListView)findViewById(R.id.listview2);
        listView2.setAdapter(mAdapter2);
        mAdapter2.notifyDataSetChanged();
        listView2.setOnItemClickListener(toBondDevices);//点击设备,进行绑定
    }
    //点击开始查找蓝牙设备
    public View findDevice(View view){
        //先执行其他代码,8s后执行run()里面的代码
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                change_Button_Text("搜索设备","ENABLE");
                mbluetoothController.stopBlueTooth(myScanCallBack);
            }
        }, 8000);
        change_Button_Text("搜索中...","DISABLE");
        mbluetoothController.scanBlueTooth(myScanCallBack);Log.e("提示","---------->findDevice");
        return view;
    }
    //点击按键搜索后按键的变化
    private void change_Button_Text(String text,String state){
        if("ENABLE".equals(state)){
            findBtn.setEnabled(true);
            findBtn.getBackground().setAlpha(255); //0~255 之间任意调整
            findBtn.setTextColor(ContextCompat.getColor(this, R.color.black));
        }
        else {
            findBtn.setEnabled(false);
            findBtn.getBackground().setAlpha(150); //0~255 之间任意调整
            findBtn.setTextColor(ContextCompat.getColor(this, R.color.colorAccent));
        }
        findBtn.setText(text);
    }
    //点击设备后执行的函数,跳转到第二个操作界面,并将选中的蓝牙设备信息传递过去
    private AdapterView.OnItemClickListener toBondDevices =new AdapterView.OnItemClickListener(){
        @Override
        public void onItemClick(AdapterView<?> adapterView, View view, int i, long l){
            mbluetoothController.stopBlueTooth(myScanCallBack);
            BluetoothDevice device = findDevices.get(i);
            Intent intent = new Intent(MainActivity.this,Main2Activity.class);
            intent.putExtra("device",device);
            startActivity(intent);
        }
    };
    //设置toast的标准格式
    private void showToast(String text){
        if(mToast == null){
            mToast = Toast.makeText(this, text,Toast.LENGTH_SHORT);
            mToast.show();
        }
        else {
            mToast.setText(text);
            mToast.show();
        }
    }
}

4、MainActivity2 .java,完整代码及其解析如下:

package com.example.wyb.bluetoothchatui;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import java.util.List;
import BluetoothPackage.BluetoothController;
public class Main2Activity extends AppCompatActivity {
    private BluetoothDevice Device;
    private TextView textView;
    private EditText editText;
    private mGattCallback myGattCallBack;
    private BluetoothGatt mybluetoothGatt;
    private BluetoothController mbluetoothController;
    private String SERVICE_EIGENVALUE_SED = "0000ffe1-0000-1000-8000-00805f9b34fb";
    private android.os.Handler mytimeHandler = new android.os.Handler();
    private BluetoothGattCharacteristic mneedGattCharacteristic;
    private TextView deviceState;
    private TextView appname;
    @Override
    protected void onDestroy() {
        //返回第一个界面时取消蓝牙配对
        mybluetoothGatt.disconnect();
        super.onDestroy();
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        Intent intent = getIntent();
        Device = intent.getParcelableExtra("device");
        textView = (TextView) findViewById(R.id.textView1);
        editText = (EditText) findViewById(R.id.input_text);
        deviceState = (TextView) findViewById(R.id.device_state);
        appname = (TextView) findViewById(R.id.AppName);
        appname.setText("");
        deviceState.setText("Device:"+Device.getName()+"(未连接)");
        mbluetoothController = new BluetoothController(this);
        myGattCallBack = new mGattCallback();
        mybluetoothGatt = mbluetoothController.connectBlueTooth(Device,false,myGattCallBack);

    }
    //蓝牙绑定功能的实现
    private class mGattCallback extends BluetoothGattCallback {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt,int status,int newState){
            Log.e("提示","蓝牙连接成功");
            super.onConnectionStateChange(gatt,status,newState);
            mytimeHandler.postDelayed(new Runnable(){
                @Override
                public void run(){
                    mybluetoothGatt.discoverServices();
                    deviceState.setText("Device:"+Device.getName()+"(已连接)");
                }
            },1000);
        }
        @Override
        public void onServicesDiscovered(BluetoothGatt gatt,int status){
            List<BluetoothGattService> services = mybluetoothGatt.getServices();
            for(int i=0;i<services.size();i++){
                Log.e("提示","第"+i+"个"+services.get(i));
                List<BluetoothGattCharacteristic> characteristics = services.get(i).getCharacteristics();
                for (int j=0;j<characteristics.size();j++){
                    Log.e("提示","第"+i+"个服务,第"+j+"个特征值"+characteristics.get(j));
                    if(characteristics.get(j).getUuid().toString().equals(SERVICE_EIGENVALUE_SED)){
                        Log.e("提示","我找到我需要的特征了");
                        mneedGattCharacteristic = characteristics.get(j);
                        mybluetoothGatt.setCharacteristicNotification(mneedGattCharacteristic,true);
                    }
                }
            }
            super.onServicesDiscovered(gatt,status);
            Log.e("提示","onServicesDiscovered");
        }
        @Override
        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic,int status){
            super.onCharacteristicRead(gatt,characteristic,status);
            Log.e("提示","onCharacteristicRead");
        }
        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic){
            super.onCharacteristicChanged(gatt,characteristic);
            Log.e("提示","onCharacteristicChanged");
        }
    }
    //发送信息的实现
    public void sendData(BluetoothGatt bluetoothGatt,BluetoothGattCharacteristic bluetoothGattCharacteristic,String text){
        bluetoothGattCharacteristic.setValue(text);
        bluetoothGatt.writeCharacteristic(bluetoothGattCharacteristic);
    }
    //处理要发送的信息,并更新界面内容
    public View sendMessage(View view){
        sendData(mybluetoothGatt,mneedGattCharacteristic,editText.getText().toString());
        textView.setText(textView.getText().toString()+editText.getText().toString());
        editText.getText().clear();
        return view;
    }
}

三、本项目的最终分享

链接:https://pan.baidu.com/s/1rX6GbTvHRU3Mb18tgwEjHg  提取码:td1d

四、后文

那么到此你就已经完成了本项目的制作,此软件制作其实没有想象中的那么难,至于STM32上的程序编写教程可以看往期博文: Bluetooth(HC)与STM32的连接通讯(在手机端通过蓝牙控制STM32板子小灯)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/476142.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HTML【前端基础】

目录 1.HTML 结构 1.1 HTML 标签 1.2 HTML 文件基本结构 1.3 标签层次结构 1.4 快速生成代码框架 2.HTML 常见标签 2.1 注释标签 2.2 标题标签: h1-h6 2.3 段落标签&#xff1a;p 2.4 换行标签: br 2.5 格式化标签 2.6 图片标签: img 2.7 超链接标签: a 2.8 表格…

Android Java 音频采集 AudioRecord

在 Android Java 应用中&#xff0c;一般用 AudioRecord 管理从平台的音频输入设备采集音频数据所需的资源。音频采集和音频播放密切关系&#xff0c;Android 系统中 Java AudioRecord 和 AudioTrack 在许多方面&#xff0c;都有着很高的相似性&#xff0c;无论是代码的目录组织…

java基础知识——25.异常

这篇文章&#xff0c;我们来讲一下java的异常体系 目录 1.异常概述 2 java的异常继承体系 2.1 编译时异常 2.2 运行时异常 2.3 底层原理 2.4 异常的作用 3.异常的处理方式 3.1 JVM默认的处理方式 3.2 自己处理&#xff08;捕获异常&#xff09; 3.2.1自己处理的4个问…

端到端NVMe?| NVMe-OF或FC-NVMe

声明 主页&#xff1a;元存储的博客_CSDN博客 依公开知识及经验整理&#xff0c;如有误请留言。 个人辛苦整理&#xff0c;付费内容&#xff0c;禁止转载。 内容摘要 前言 NVMe全称是Nonvolatile Memory Express&#xff08;非易失性内存标准&#xff09;&#xff0c;在它首次…

( 字符串) 647. 回文子串 ——【Leetcode每日一题】

❓647. 回文子串 难度&#xff1a;中等 给你一个字符串 s &#xff0c;请你统计并返回这个字符串中 回文子串 的数目。 回文字符串 是正着读和倒过来读一样的字符串。 子字符串 是字符串中的由连续字符组成的一个序列。 具有不同开始位置或结束位置的子串&#xff0c;即使…

JSP 的本质原理解析:“编写的时候是JSP,心里想解读的是 java 源码“

JSP 的本质原理解析&#xff1a;“编写的时候是JSP&#xff0c;心里想解读的是 java 源码” 文章目录 JSP 的本质原理解析&#xff1a;"编写的时候是JSP&#xff0c;心里想解读的是 java 源码"每博一文案1. JSP 概述2. 第一个 JSP 程序3. JSP 的本质就是 Servlet4. J…

appium的手动安装步骤教程及appium-doctor报错解决集合

前言 相信你不少软件测试行业小伙伴应该在用npm安装appium或者是cpm安装appium途中也碰到下面一些报错吧&#xff0c;接下来Darren洋教你改为手动安装appium吧&#xff01;整理不易&#xff0c;请点赞加关注后查看。 一、安装Node.js 下载地址&#xff1a; Previous Releases …

【五一创作】某头条参数破解并实现界面化搭建

某条参数破解并实现界面化搭建 前言效果展示难点参数逆向破解_signatureac_signatures_v_web_id 界面化实现总结 前言 趁着日常闲余时间&#xff0c;想着搞一搞某条的反爬&#xff0c;练练手&#xff0c;想到自己很久没开发过前端界面了&#xff0c;有点生疏&#xff0c;也趁此…

PCL学习二:PCL基础应用教程

参考引用 PCL Basic UsagePCL 点云库官网教程 1. pcl_viewer 基本使用 1.1 pcl_viewer 安装测试 pcl_data 源码克隆$ git clone https://github.com/PointCloudLibrary/data.git进入 /pcl_data/tutorials&#xff08;如下图&#xff09;$ cd ~/pcl_data/tutorials # 此处为重…

IDEA常用提升效率的操作小记

IDEA目前是使用最广泛的Java开发工具之一了&#xff0c;虽然是收费的&#xff0c;但是也提供了免费的社区版&#xff0c;并且收费版也支持使用github的开源项目&#xff0c;使用免费license&#xff0c;虽然每年都要续&#xff0c;我用的就是开源项目申请的免费license。 开发…

【Pytorch基础教程39】torch常用tensor处理函数

note 文章目录 note一、tensor的创建二、tensor的加减乘除三、torch.argmax()函数四、gathter函数小栗子1小栗子2&#xff1a;如果每行需要索引多个元素&#xff1a; 四、针对某一维度的操作五、改变维度、拼接、堆叠等操作Reference 一、tensor的创建 torch.tensor会复制data…

STM32配置ADC2(DMA)进行采集 DAC 输出-2

0.一定要先看上一节&#xff1a;STM32配置ADC2&#xff08;DMA&#xff09;进行采集 DAC 输出-2 1.实验目标 在上一节的基础上&#xff0c;我们把 DAC&#xff08;三角波&#xff09;给集成进来&#xff0c;实现按下按键输出三角波&#xff0c;通过串口发送数据给电脑&#x…

Apache Zeppelin系列教程第二篇——整体架构

Zeppelin 架构&#xff1a; 首先我们来了解下 Zeppelin的架构, Zeppelin 主要分3层。 Web前端 Zeppelin Server Interpreter Zeppelin前端负责前端页面的交互&#xff0c;通过Rest API 和WebSocket的方式与Zeppelin Server进行交互。 Zeppelin Server是一个Web server&…

【python 基础语法一】注释,变量与运算符

一、注释 注释: 就是对代码的解释&#xff0c;方便阅读&#xff0c;被注释的代码不执行 分类 单行注释 # 1.单行注释 以#号开头 &#xff0c;右边的所有东西都被当做说明文字 &#xff0c;程序不进行编译运行。 print(hello world)多行注释 # 2.多行注释 三个单引号 或…

Sentinel源码分析学习

文章目录 前言Sentinel源码分析1.Sentinel的基本概念1.1.ProcessorSlotChain1.2.Node1.3.Entry1.3.1.自定义资源1.3.2.基于注解标记资源 1.4.Context1.4.1.什么是Context1.4.2.Context的初始化1.4.2.1.自动装配1.4.2.2.AbstractSentinelInterceptor1.4.2.3.ContextUtil 2.Proce…

django显示echart图表:柱状图、折线图、饼图、地图、词云

django显示echart图表 效果: 示例demo 点我查看 1、urls.py 其中关键代码: urlpatterns = [path("book_chart/", views.book_chart, name="book_cha

LINUX压缩和解压和磁盘管理与维护命令

文章目录 一、压缩和解压命令二、磁盘管理与维护命令总结 一、压缩和解压命令 Linux zip命令:压缩文件或目录 Linux unzip命令:解压文件或目录 Linux tar命令:归档工具 二、磁盘管理与维护命令 Linux df命令:显示磁盘空间使用情况 Linux mount命令:挂载文件系统 Linux quota命…

APK文件结构

文件结构 assets文件用来存放需要打包到Android 应用程序的静态资源文件&#xff0c;例如图片资源文件&#xff0c;JSON配置文件&#xff0c;渠道配置文件&#xff0c;二进制数据文件&#xff0c;HTML5离线资源文件等 与res/raw目录不同的数&#xff0c;assets目录支持任意深度…

数位dp。

一&#xff0c;思想&#xff1a; 在处理1e9甚至1e18,1e100的问题时&#xff0c;因为在统计情况下有很多重复的计算&#xff0c;数位dp实现了相同状态只计算一次&#xff0c;从而大幅减少运算时间&#xff0c;思想就是对每一位进行dp&#xff0c;计算时记忆化每一位可以有的状态…