低功耗(BLE)蓝牙模块开发之路

2020/7/16更新:项目翻盘

一 项目流程概览:(已经将本项目中的敏感技术剔除,转化成通用的技术方案)

  • 申请权限(位置权限、提示使用蓝牙需要打开位置信息。打开蓝牙的权限,)
  • 扫描蓝牙
  • 连接/断开蓝牙
  • 接收/发送数据
  • 解析数据
  • 以图表动态的显示数据的变化
  • 保存数据到excel中

1.申请权限
蓝牙权限:在manifest/AndroidManifest.xml中添加需要的所有权限(建议直接复制):

    <!-- 蓝牙、位置所需权限 -->
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <!-- 在Android6.0 定位权限申请 -->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <!-- 在Android10.0里,获取定位权限需要增加以下权限。 -->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <!-- <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/> -->
    <!-- 过滤掉没有蓝牙BLE功能的蓝牙按时required为true时,则应用只能在支持BLE的Android设备上安装运行;required为false时,Android设备均可正常安装运行,需要在代码运行时判断设备是否支持BLE feature: -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
        <uses-feature
        android:name="android.hardware.bluetooth_le"
        android:required="true" />


蓝牙权限申请如上图所示

2.扫描蓝牙

  • 扫描过程通过回调来实现,因此基础不孬者希望你自己去补充这方面的知识。
    【笔者比较笨,学了一周才理清楚一点】
    大致思路:在扫描页面中去实现bleutil类(一个将ble所有操作集中到一个地方的类,见源代码)的回调函数。
    /**
     * 扫描回调注册
     */
    private void scanBle() {
        bleConnectUtil.bluetoothIsAble(new MyBleCallBack() {
            @Override
            public void callbleBack(BluetoothDevice device) {
                Log.d(TAG,"--->搜索出蓝牙名字为:"+ device.getName());//device.getName():bleutil扫描到的蓝牙名字
                if (device == null) {
                    return;
                }

                boolean NoAdd = true;//健壮性,可忽略
                for (BluetoothDevice oldDevice : listDevice) {//若多次扫描到同一设备。只显示一次
                    if (oldDevice.getAddress().equals(device.getAddress())) {
                        NoAdd = false;
                        break;
                    }
                }
                if (NoAdd) {//第一次扫描则将其加入到设备列表中去,listDevice等杂七杂八的见源代码
                    listDevice.add(device);
                    listDeviceName.add(device.getName());
                    adapter.notifyDataSetChanged();//更新页面中的dataset
                }
            }
        });
    }


3.连接蓝牙

  • 用户通过点击列表中的设备从而进行蓝牙连接
//这个是重载。抄下来就Ok了
   public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        bleConnectUtil.stopScan();//stop scan
        selectPos = position;
        dialog.show();//不用管,流畅性处理。
        bleConnectUtil.connectBle(listDevice.get(position));//连接蓝牙,在bleutil那边会返回一个handler数值,在本页面中接受这个handler就知道是否接受成功了。
    }
//-------------------------------------------------检查连接结果----------------------------------------------------
1.子线程发送连接请求
    /**
     * Connet子线程、当点击蓝牙名字后重复向蓝牙发送链接请求,超过2次未成功发送连接超时msg给handler,成功则不用管
     */
    Runnable checkConnetRunnable = new Runnable() {
        @Override
        public void run() {
            if (!bleFlag) {
                //>2
                if (regainBleDataCount > 2) {
                    //发送带标记的消息(内部创建了message,并设置msg.what = 1000)
                    handler.sendEmptyMessage(1000);
                } else {
                    regainBleDataCount++;
                    sendDataByBle(currentSendOrder, "");
                    //延时三秒发送消息
                    handler.postDelayed(checkConnetRunnable, 3000);
                }
            }
        }
    };

2.连接成功由bleutil通过eventbus向mainActivity发送成功信息和特性来完成。
    /**
     * 扫描蓝牙事件处理
      * @param eventMsg :接收蓝牙连接情况
     */
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onEvent(EventMsg eventMsg) {
        switch (eventMsg.getMsg()) {
            case Constants.BLE_CONNECTION_FINISH_MSG:
                if (dialog != null) {
                    dialog.dismiss();
                }
                bleConnectUtil.stopScan();
                btnScan.setText("扫描设备");

//               todo 已连接,此处的bleConnectUtil是如何变成充满连接信息的呢?我理解的是bleutil用了main的bleConnectUtil去执行连接操作,人走,茶在。
                if (bleConnectUtil.isConnected()) {
                    Toast.makeText(MainActivity.this, "连接成功", Toast.LENGTH_SHORT).show();
                    tvBleName.setVisibility(View.VISIBLE);
                    tvBleName.setText("您所连接的设备是:" + listDeviceName.get(selectPos));
                    listview.setVisibility(View.GONE);
//                    写一个接受数据的回调。bleUtil一旦接受到数据便使用mainActivity里面的blecallback方法来处理数据.
                    bleConnectUtil.setCallback(blecallback);
                }
                //未连接成功
               else Toast.makeText(MainActivity.this, "连接失败", Toast.LENGTH_SHORT).show();
                break;
            default:
                break;
        }
    }


 3.连接蓝牙相关其他情况(断开连接,超时)用Handler处理----------------------------------------------
    Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
//收到消息,不是连接成功的提示,是收到蓝牙发送过来的消息!
                case 10:
                    dialog.dismiss();
                    tvReceiver.setText(msg.obj.toString());

                    Log.d(TAG, "hand receive:"+msg.toString());
                    EventBus.getDefault().postSticky(new DataMsg(msg.obj.toString()));
                    break;
//连接超时 ,子线程向蓝牙发送链接请求,超时会给它发送1000,便是在这儿接收          
                case 1000:
                    regainBleDataCount = 0;//这些是我自己写的标志符。不需要你去理解
                    bleFlag = false;//这些是我自己写的标志符。不需要你去理解
                    handler.removeCallbacks(checkConnetRunnable);//不用再发送链接请求
                    if (dialog != null) {
                        dialog.dismiss();
                    }
                    //超时处理
					//Toast.makeText(MainActivity.this, "超时请重试!", Toast.LENGTH_SHORT).show();
                    break;
 //断开连接,或者发送数据失败
                	case 1111:
                  //断开连接处理
                    bleConnectUtil.disConnect();
                    break;
                default:
                    break;
            }
        }
    };

4.发送数据
发送数据未经过脱敏。这里放一个发送demo函数


    private void sendDataByBle_hex(final byte[] currentSendAllOrder) {
        final boolean[] isSuccess = new boolean[1];//一个bool大小的发送状态标志符
        mBluetoothGattCharacteristic.setValue(currentSendAllOrder);//currentSendAllOrder即发送的字符串
        isSuccess[0] = bleConnectUtil.sendData(mBluetoothGattCharacteristic);//接受发送状态

        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                if (!isSuccess[0]) {
                    dialog.dismiss();
                    handler.sendEmptyMessage(1111);//发送失败
                }
                Log.e("--->", "是否发送成功:" + isSuccess[0]);
            }
        }, (currentSendAllOrder.length / 40 + 1) * 15);//间隔一段时间再发送

    }

5.接受数据

由3.连接部分知我已经写好了接收数据的流程了。接下来补充一下具体的处理函数
    /**
     * 接受数据后的回调处理函数(mainactivity提供环境,bleutil来执行函数)
     */
    private BleConnectionCallBack blecallback = new BleConnectionCallBack() {
        @Override
        public void onRecive(BluetoothGattCharacteristic data_char) {
            bleFlag = true;
            //收到的数据
            byte[] receive_byte = data_char.getValue();
//            Log.d(TAG, "接受到的原始数据:"+receive_byte);
//
            String str = CheckUtils.toHexString1(receive_byte);
//            Log.d(TAG, "数据转化成16进制 is:"+str);

            //利用handler传值
            Message message = new Message();
            message.obj = str;
            message.what = 10;
            handler.sendMessage(message);
        }
        @Override
        public void onSuccessSend() {
            //数据发送成功
            Log.e(TAG, "onSuccessSend: ");
        }
        @Override
        public void onDisconnect() {
            //设备断开连接
            Log.e(TAG, "onDisconnect: ");
            Message message = new Message();
            message.what = 1111;
            handler.sendMessage(message);
        }
    };

项目已经完成,但是时间关系无法一次讲完。努力将代码脱敏,上传,更新ing。任何疑问可留言。笔者在CSDN买了房的。哈哈哈

  • 蓝牙扫描
  • 蓝牙连接,断开
  • 发送,接受数据
  • 根据CRC8算法解析,校验数据,利用折线图(MPchart)动态实时展示数据如电压,电流,压力等,对折线图的造作数据导出成excel并保存在本地
  • 批量发送设置参数信息到单片机串口,从而完成APP远程设置单片机的外设相关参数
  • 待续

骚话:

毕业菜鸟刚入职,在网上找了大大小小的安卓蓝牙BLE开源项目十几个了。都没有自己喜欢的,现在的计划是自己动手做一个。并且每天将自己学会的东西、认为项目中很重要的东西记录下来。(下面的项目都能运行,但是别人写的都是后台demo。美工和功能性不支持拿来即用,也怪不了别人。我自己加把劲弄一个出来吧)

在这里插入图片描述

  • 前三天的开发全是去Pilipli找视频资料、百度一些开源项目-努力让他们运行-测试手机和电脑能不能通过软件联通(这之间的蓝牙通讯称为经典蓝牙)。
    【因为发现自己做的蓝牙芯片叫JDY 10M,是BLE低功耗蓝牙,几天的努力化为泡沫】

2020/6/11日自己下定决心找一篇写的好一点的教程狠狠的阅读。十遍不行就二十遍!

奉上我拜读的文章: Android开发蓝牙与ble设备的通讯.

#这篇文章我也下载了他的源码来解读,后台功能很职场,需要有一些开发基本思维。(只是涉及到作者将功能模块分成许多文件,目的是解耦合和多复用性)

这里我不讲怎么去做。只是记录下自己每天学习的哪些关键点。项目完成再开贴去讲从头到尾做一个ble蓝牙安卓调试器


2020/6/12:

  • 改变自己一贯的白嫖、能用就Ok的思想,决定死抓一篇文章,分析他用了哪些函数(而不是关注函数内部是怎么运行的)
  • 读懂流程之后分析项目是怎样运行的。重点分析了安卓的回调机制。
  • 分析hander在安卓中的作用。

2020/6/13:

  • 昨天将app的功能基本实现了数据收发功能。死磕了两天的安卓回调。慢慢的心里面对回调机制有一定的了解,但是感觉自己还是不能分析出回调在源码中的运用。先跳过这个
  • 晚上2点多还在改导航栏。哎,自己太笨了,一直没把它和原来的项目结合着弄出来。心里别提多难过了、加油吧。

待记录问题:


安卓部分:

  • 蓝牙通讯数据包-进制与数据类型关系/数据包的解析/
  • char数组/String/怎么输出的
  • 单片机给上位机发送的数据通过CRC8循环校验的,我在APP里怎么去验证数据是否正确呢?
  • 折线图表要想显示成功,需要在界面处将多余的表的VISIBLE设置成GONE
  • 设置界面的数据输入。digitsl参数利用正则可以限定输入的内容、inputtype参数可以设置输入的软键盘类型。

调试部分:

  • USB转TTL上面有几个按键,记得把USB播成on、将48拨成off、蓝牙是接3.3V.这些一旦弄错便会烧掉芯片。切记
  • 将蓝牙芯片和USB转TTL、串口波特率选115200、勾中发送新行(电脑上的助手才这样)、打开串口,做完以上步骤开始调试。
  • (更名)发送 AT+NAME=BlueTest( 也可以这样:AT+NAMEBlueTest)
  • (波特率)因为单片机无法发送太快,需要设置波特率为9600、发送AT+BAUD=4(AT+BAUD4)
  • (保存设置)AT+RESET
附波特率参数设置表
Param:(0-7)
0:115200 bps
1:57600 bps
2:38400 bps
3:19200 bps
4:9600 bps
5:4800 bps
默认值:0

硬件部分:

  • DC电源牢记先连线再通电

备注:

我知道可以通过把原来的数据部分截取出来进行CRC算法得到的值和原来的尾部的校验值比较,来实现验证。但是我觉得接收数据没必要这么复杂,
我在网上找的CRC8资料可以:(数据+校验码)%多项式特征值==0。
如果余为零的话就证明数据无误。

  • APP界面跳转的时候以下两句一定要在OnCreate()前面。
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

总结: byte 8位,int 32位,char 16位,short 16位 都是数,只是长度不同,可以相互转换
2进制,8进制,16进制,只是一个数输出的进制方法,数值还是那个数
byte,int,char等的都是数据类型,进制只是数的表现形式

一个BYTE占8位。而16进制由四个二进制表示。所以说呀、一个BYTE能表示两位16进制数据。例如:byte[0]=‘2’ -》 0000 0010
任何疑问联系QQ: 498661202

  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值