Andriod 使用fastBLE实时获取ibeacon RSSI

前言

最近,物联网通信课上需要自己做一个蓝牙定位的小东西,技术原理也简单,直接使用很久以前苹果公司提出的一项叫ibeacon的技术,进行定位时,需要几个蓝牙基站不断广播信号,使用者通过手机扫描广播,不需要直接与基站连接,就可以直接获得信号强度数据RSSI,以及基站蓝牙广播的一些附带数据。本文我不详细描述定位的技术原理,主要集中于描述android获取基站蓝牙RSSI数据的实现全过程。

一 BLE(Bluetooth Low eneger)

BLE即低功耗蓝牙。2010年推出的蓝牙4.0版本开始支持BLE,2016年推出的蓝牙5.0针对BLE技术进行了相应的提升与优化,主要是物联网方向上的改进。Android 从Android4.3版本开始支持BLE,但仅支持Central Mode(中心模式),Android5.0后开始支持Perpheral Mode(外设模式)。下面会列出主要的概念,BLE的具体概念解释请自行参考中文版解释, 英文原文
1.1
中心模式(Central Mode):
Android端作为中心设备,连接其他外围设备。

外设模式 (Peripheral Mode)
Android端作为外围设备,被其他中心设备连接。

GAP(Generic Access Profile)
它用来控制设备连接和广播。 GAP 中蓝牙设备可以向外广播数据包.
广播包分为两部分
Advertising Data Payload(广播数据)和 Scan Response Data Payload(扫描回复)外围设备会设定一个广播间隔,每个广播间隔中,它会重新发送自己的广播数据。广播间隔越长,越省电,同时也不太容易扫描到。

两种设备间的交互方式
完全基于广播的方式
不需要连接的,只要外设广播自己的数据即可。用这种方式主要目的是让外围设备,把自己的信息发送给多个中心设备。基于非连接的,这种应用就是依赖 BLE 的广播,也叫作 Beacon。这里有两个角色,发送广播的一方叫做 Broadcaster,监听广播的一方叫 Observer。

基于GATT连接的方式
外设通过广播自己来让中心设备发现自己,并建立 GATT 连接,从而进行更多的数据交换。这里有且仅有两个角色,发起连接的一方,叫做中心设备—Central,被连接的设备,叫做外设—Peripheral。GATT 连接是独占的。也就是一个 BLE 外设同时只能被一个中心设备连接。一旦外设被连接,它就会马上停止广播,这样它就对其他设备不可见了。

ATT
全称 attribute protocol,中文名“属性协议”。它是 BLE 通信的基础。ATT 把数据封装,向外暴露为“属性”,提供“属性”的为服务端,获取“属性”的为客户端。ATT 是专门为低功耗蓝牙设计的,结构非常简单,数据长度很短。

GATT
全称 Generic Attribute Profile, 中文名“通用属性配置文件”。它是在ATT 的基础上,对 ATT 进行的进一步逻辑封装,定义数据的交互方式和含义。GATT是我们做 BLE 开发的时候直接接触的概念。
商业转载请联系作者获得授权,非商业转载请注明出处。

GATT 层级
GATT按照层级定义了4个概念:配置文件(Profile)、服务(Service)、特征(Characteristic)和描述(Descriptor)。他们的关系是这样的:Profile 就是定义了一个实际的应用场景,一个 Profile包含若干个 Service,一个 Service 包含若干个 Characteristic,一个 Characteristic 可以包含若干 Descriptor。

Profile
Profile 并不是实际存在于 BLE 外设上的,它只是一个被 Bluetooth SIG 或者外设设计者预先定义的 Service 的集合。

Service
Service 是把数据分成一个个的独立逻辑项,它包含一个或者多个 Characteristic。每个 Service 有一个 UUID 唯一标识。

Characteristic
需要重点提一下Characteristic, 它定义了数值和操作,包含一个Characteristic声明、Characteristic属性、值、值的描述(Optional)。通常我们讲的 BLE 通信,其实就是对 Characteristic 的读写或者订阅通知。比如在实际操作过程中,我对某一个Characteristic进行读,就是获取这个Characteristic的value。
载请联系作者获得授权,非商业转载请注明出处。

UUID
Service、Characteristic 和 Descriptor 都是使用 UUID 唯一标示的。

二 FastBLE

Android中进行BLE编程,可以使用原生的API,也可以使用第三开发者开源的第三方包,这里使用的是fastBLE.
fastBLE github开源链接为https://github.com/Jasonchenlijian/FastBle。fastBLE开源包的作者页写了一篇文Android BLE开发详解和FastBle源码解析.因为我们只需要获取RSSI的数据,所以这里只介绍到fastBLE扫描广播的部分,若要进行设备间的通信,请行研究Android BLE开发详解和FastBle源码解析

在Android Studio上添加fastBLE,可以直接添加fastBLE的jar包,jar可以在fastBLE的github下载,
请添加图片描述
FastBLE-2.3.4.jar 添加到app的libs文件夹里
请添加图片描述
jar包复制进去后,选中你的jar包,右键,选择add as library,放进你的module中(要是有多个module,要注意自己要放进哪个module),然后加载下就可以了,下图所示,说明jar包添加成功了。
请添加图片描述

添加完jar包后,使用FastBLE,需要进行一下流程

  1. 添加Android权限到AndroidManifest.xml
  2. oncreate初始函数中动态申请权限(这个很重要,不然后面fastBLE的代码几乎相当于失效)
  3. 初始化及配置
  4. 判断当前Android设备是否支持BLE
  5. 判断当前Android设备的蓝牙是否已经打开
  6. 主动打开蓝牙
  7. 配置扫描规则
  8. 开始进行扫描

三 使用fastBLE实时获取RSSI

1.添加权限到AndroidManifest.xml

把下面权限添加到app的AndroidManifest.xml文件中

	<uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

2.oncreate初始函数中动态申请权限

这个步骤很重要,因为Google在 Android 6.0 开始引入了权限申请机制,将所有权限分成了正常权限和危险权限。应用的相关功能每次在使用危险权限时需要动态的申请并得到用户的授权才能使用。
下面是我对与蓝牙使用权限的动态申请实现代码,代码可以直接添加到 protected void onCreate(Bundle savedInstanceState) 函数里使用。

        String[] permissions = {Manifest.permission.BLUETOOTH,
                Manifest.permission.BLUETOOTH_ADMIN,
                Manifest.permission.ACCESS_COARSE_LOCATION,
                Manifest.permission.ACCESS_FINE_LOCATION};
        //验证是否许可权限
        for (String str : permissions) {
            if (this.checkSelfPermission(str) != PackageManager.PERMISSION_GRANTED) {
                //申请权限
                this.requestPermissions(permissions, 101);
            }
        }

3.fastBLE初始化及配置

在使用fastBLE之前,必须进行初始化设置,可以进行一些自定义的配置,比如是否显示框架内部日志,重连次数和重连时间间隔,以及操作超时时间。下面是初始化代码

		//初始化fastBLE
        BleManager.getInstance().init(getApplication());
        BleManager.getInstance()
                .enableLog(true)
                .setReConnectCount(1, 5000)
                .setConnectOverTime(20000)
                .setOperateTimeout(5000);

4.判断当前Android设备是否支持BLE

BleManager.getInstance().isSupportBle();

5.判断当前Android设备的蓝牙是否已经打开

BleManager.getInstance().isBlueEnable();

6.主动打开蓝牙

BleManager.getInstance().enableBluetooth();

7.配置扫描规则

 BleScanRuleConfig scanRuleConfig = new BleScanRuleConfig.Builder()
          .setServiceUuids(serviceUuids)      // 只扫描指定的服务的设备,可选
          .setDeviceName(true, names)         // 只扫描指定广播名的设备,可选
          .setDeviceMac(mac)                  // 只扫描指定mac的设备,可选
          .setAutoConnect(isAutoConnect)      // 连接时的autoConnect参数,可选,默认false
          .setScanTimeOut(10000)              // 扫描超时时间,可选,默认10秒,若小于0,则时间无限,一直扫描,但可以用BleManger.getInstance().cancelScan()来中止扫描
          .build();
  BleManager.getInstance().initScanRule(scanRuleConfig);

8.开始进行扫描

onScanStarted(boolean success):
会回到主线程,参数表示本次扫描动作是否开启成功。由于蓝牙没有打开,上一次扫描没有结束等原因,会造成扫描开启失败。
onLeScan(BleDevice bleDevice):
扫描过程中所有被扫描到的结果回调。由于扫描及过滤的过程是在工作线程中的,此方法也处于工作线程中。同一个设备会在不同的时间,携带自身不同的状态(比如信号强度等),出现在这个回调方法中,出现次数取决于周围的设备量及外围设备的广播间隔。
onScanning(BleDevice bleDevice):
扫描过程中的所有过滤后的结果回调。与onLeScan区别之处在于:它会回到主线程;同一个设备只会出现一次;出现的设备是经过扫描过滤规则过滤后的设备。
onScanFinished(List<BleDevice> scanResultList)
本次扫描时段内所有被扫描且过滤后的设备集合。它会回到主线程,相当于onScanning设备之和。

BleManager.getInstance().scan(new BleScanCallback() {
      @Override
      public void onScanStarted(boolean success) {
      }

      @Override
      public void onLeScan(BleDevice bleDevice) {
      }

      @Override
      public void onScanning(BleDevice bleDevice) {
      }

      @Override
      public void onScanFinished(List<BleDevice> scanResultList) {
      }
  });

四 开源例子

1.app截图

图中的label为rssi信号
输入框为要扫描的蓝牙信标mac地址

请添加图片描述

2.MainActivity.java

package com.libufan.rssitest;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import com.clj.fastble.BleManager;
import com.clj.fastble.callback.BleScanCallback;
import com.clj.fastble.data.BleDevice;
import com.clj.fastble.scan.BleScanRuleConfig;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    public Button btn1,btn2;
    public TextView t1,t2;
    public EditText edit;
    public String msg = "";
    public String mac = "";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        /***********************申请权限***********************************/
        String[] permissions = {Manifest.permission.BLUETOOTH,
                Manifest.permission.BLUETOOTH_ADMIN,
                Manifest.permission.ACCESS_COARSE_LOCATION,
                Manifest.permission.ACCESS_FINE_LOCATION};
        //验证是否许可权限
        for (String str : permissions) {
            if (this.checkSelfPermission(str) != PackageManager.PERMISSION_GRANTED) {
                //申请权限
                this.requestPermissions(permissions, 101);
            }
        }
        /******************************************************************/
        //初始化fastBLE
        BleManager.getInstance().init(getApplication());
        BleManager.getInstance()
                .enableLog(true)
                .setReConnectCount(1, 5000)
                .setConnectOverTime(20000)
                .setOperateTimeout(5000);

        /******************************************************************/
        //绑定控件
        btn1 = (Button)findViewById(R.id.button3);
        btn2 = (Button)findViewById(R.id.button4);
        t1 = (TextView)findViewById(R.id.textView3);
        t2 = (TextView)findViewById(R.id.textView4);
        edit = (EditText)findViewById(R.id.editText2);

        //开启广播扫描
        btn1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                checkBluetooth();
                mac = edit.getText().toString();
                //mac = "BE:AC:10:00:00:02";
                initScanner();
                startScan();
                t2.setText("open scan!");
            }
        });

        //关闭扫描
        btn2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                checkBluetooth();
                BleManager.getInstance().cancelScan();
                t2.setText("stop scan successful!");

            }
        });
    }

    //检测是否开启蓝牙
    protected void checkBluetooth(){
        if(BleManager.getInstance().isBlueEnable()){
            t2.setText("蓝牙已打开!");
        }else{
            BleManager.getInstance().enableBluetooth();
            t2.setText("蓝牙打开成功");
        }
    }

    //初始化广播搜索设置
    protected void initScanner(){
        BleScanRuleConfig scanRuleConfig = new BleScanRuleConfig.Builder()
                .setScanTimeOut(-1)              // 扫描超时时间,可选,默认10秒
                .build();
        BleManager.getInstance().initScanRule(scanRuleConfig);
    }

    //开始扫描
    protected  void startScan(){

        BleManager.getInstance().scan(new BleScanCallback() {
            @Override
            public void onScanStarted(boolean success) {
                if(success){
                    t2.setText("start scan successful!");
                }else{
                    t2.setText("start scan fail!");
                }
            }

            @Override
            public void onLeScan(BleDevice bleDevice) {
                msg = ""+bleDevice.getRssi();
                if(mac.equals(bleDevice.getMac())){
                    t1.setText(msg);
                }else{
                    //t1.setText(msg);
                }
            }

            @Override
            public void onScanning(BleDevice bleDevice) {
            }

            @Override
            public void onScanFinished(List<BleDevice> scanResultList) {
                t2.setText("stop scan successful!");
            }
        });
    }
}

3.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/base"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">


    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <Space
            android:layout_width="match_parent"
            android:layout_height="200px" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="200px"
            android:orientation="horizontal">

            <Space
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1" />

            <TextView
                android:id="@+id/textView3"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:padding="20px"
                android:text="-31"
                android:textAlignment="center"
                android:textSize="24sp" />

            <Space
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1" />
        </LinearLayout>

        <Space
            android:layout_width="match_parent"
            android:layout_height="200px" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="200px"
            android:orientation="horizontal">

            <Space
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1" />

            <EditText
                android:id="@+id/editText2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:ems="10"
                android:inputType="textPersonName" />

            <Space
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1" />
        </LinearLayout>

        <Space
            android:layout_width="match_parent"
            android:layout_height="200px" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="200px"
            android:orientation="horizontal">

            <Space
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1" />

            <Button
                android:id="@+id/button3"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="扫描" />

            <Space
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1" />

            <Button
                android:id="@+id/button4"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="停止" />

            <Space
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1" />
        </LinearLayout>

        <Space
            android:layout_width="match_parent"
            android:layout_height="200px" />

        <TextView
            android:id="@+id/textView4"
            android:layout_width="match_parent"
            android:layout_height="200px"
            android:text="this a msg!"
            android:textAlignment="center" />
    </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值