# BeaconSET Plus Android开发套件说明

  • 持续更新中...

本套SDK仅支持基于miniBeaconPlus固件的设备,不再支持其他公司生产的产品。相对于之前的单一通道设备SDK来说,现在的这一套BeaconSET Plus SDK支持的特性更多。当然开发过程中也遇到过诸多问题,我们会在本文档说明设计缘由以及注意事项。所以,请仔细阅读此文档以便尽快上手进行项目开发。

请按需阅读以下每个部分,第一部分我们会说明这套SDK的整体架构,第二部分会帮助开发者上手开发,第三部分会提及开发过程中的注意事项,第四部分可以查阅到错误码的对应说明。

# 设计说明

我们在SDK里把手机对设备的操作分成了两个阶段:广播阶段和连接阶段。为了便于理解,我们先来看看我们都提供了哪些可以操作的类以及它们的职责:

MTCentralManager: 全局管理,可以检查当前手机的蓝牙状态,监听系统的蓝牙状态改变。另外最重要的是可以扫描以及连接到这些设备;

MTPeripheral: 设备实例,当CentralManager发现一个新设备时,会对应为其生成一个MTPeripheral实例,以真实的物理设备为单位,一个设备对应一个MTPeripheral实例。它包括了MTConnectionHandler和MTFrameHandler两个属性(下文会继续描述这两个属性的作用)。

MTFrameHandler:广播包解析,这个类可以解析某个设备所广播出来的所有数据包,也就是说,它是扫描阶段的核心,所有由MTCentralManager扫描出来的广播数据都由它来解析成对应的数据帧实例。

MTConnectionHandler:连接状态操作,所有连接上设备后发生的操作都是由此类完成,包括监听连接状态改变,读取数据,写入数据等。

MTSensorHandler:传感器操作,所有连接上设备后发生的传感器操作都是由此类完成,包括读取温湿度和设置红外参数。

MinewFrame:数据帧,每个此类(或者子类)实例都对应一种数据帧,一般会由MTFrameHandler生成,如果设备正在广播多种数据帧,就会生成多个对应的MinewFrame实例。这些实例可以通过MTFrameHandler获取到。

MTOTAManager:空中升级,此类只能用来更新设备固件,它操作的是MTConnectionHandler实例。

# 整体架构图:

BeaconSET+

# 广播阶段

这个阶段主要是扫描周围的miniBeaconPlus设备和解析这些设备上的广播数据,首先MTCentralManager发起扫描,MTCentralManager会为每一个物理设备生成一个MTPeripheral实例,开发者可以通过它的MTFrameHandler属性拿到关于此设备的所有广播数据。

# 连接阶段

当MTCentralManager扫描到物理设备并生成MTPeripheral实例后,就可以连接到设备,此时针对设备的连接操作都是通过MTConnectionHandler来完成的。

# OTA单独说明

常规的OTA逻辑比较复杂,所以单独封装了MTOTAManager来执行OTA操作,必须在已经和设备建立连接的状态下才能进行。

# 开始上手

# 准备工作

# 开发环境:

  • Android Studio
  • minSdkVersion 21

# 导入到工程:

  1. 将开发套件MTBeaconPlus.jar文件拷贝到libs目录下,然后在build.gradle中添加依赖。如下图所示:

    make jar

    add jar

    在20180502版本后,dfu功能将采用外部依赖方式。请在build.gradle下添加

//添加依赖库
dependencies {
    implementation files('libs/MTBeaconPlus.jar')
    implementation 'no.nordicsemi.android:dfu:1.6.1'
}
1
2
3
4
5

否则空中升级时将会crash。app混淆时请在混淆文件中加入以下代码:

   keep public class no.nordicsemi.android.**{*;}
1
  1. AndroidManifest.xml 文件下添加蓝牙权限和相应的组件注册。如下所示:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

//targetSdkVersion版本选择 31(Android 12)蓝牙权限用如下方式添加
<uses-permission android:name="android.permission.BLUETOOTH"
    android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
    android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
    tools:targetApi="s" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<service android:name="com.minew.beaconplus.sdk.ConnectService"/>
<service android:name="com.minew.beaconplus.sdk.services.DfuService"/>
<receiver android:name="com.minew.beaconplus.sdk.receivers.BluetoothChangedReceiver">
    <intent-filter>
        <action android:name="android.bluetooth.adapter.action.STATE_CHANGED"/>
    </intent-filter>
</receiver>
1
2
3
4
5
6
7

# 开始开发

# 获取管理类单例

首先开发者需要获取一个管理类单例,并且做好开发者需要的监听:

// 获取管理类单例
MTCentralManager mtCentralManager = MTCentralManager.getInstance(this);

// 如果需要监听系统蓝牙状态变化请这样做
// *** 还可以通过bluetoothstate属性主动获取
// *** 仅当state == PowerStatePoweredOn时开发套件才可正常工作
mtCentralManager.setBluetoothChangedListener(new OnBluetoothStateChangedListener() {
  @Override
  public void onStateChanged(BluetoothState state) {
  }
});
1
2
3
4
5
6
7
8
9
10
11

# 扫描设备

需要通过扫描来发现附近的设备,这样才能获取它们的广播内容,连接并修改设备参数等。

当前已在扫描状态尝试启动扫描,返回“操作失败:当前处于扫描状态,无需重复启动扫描。”

//开始扫描
mtCentralManager.startScan();
// 遍历数组,获取每个设备实例
// 也可以把它们绑定到UI上,将所有数据显示出来。
mtCentralManager.setMTCentralManagerListener(new MTCentralManagerListener() {
    @Override
    public void onScanedPeripheral(final List<MTPeripheral> peripherals) {
        for (MTPeripheral mtPeripheral : peripherals) {
            // 获取设备广播实例
            MTFrameHandler mtFrameHandler = mtPeripheral.mMTFrameHandler;
            String mac = mtFrameHandler.getMac(); 		//设备Mac地址
            String name = mtFrameHandler.getName();		//设备名称
            int battery = mtFrameHandler.getBattery();	//设备电池电量
            int rssi = mtFrameHandler.getRssi();		//rssi
            long lastUpdate = mtFrameHandler.getLastUpdate();  //最近更新时间
            // 设备广播的所有数据帧(iBeacon,UID,URL...)
            ArrayList<MinewFrame> advFrames = mtFrameHandler.getAdvFrames();
            for (MinewFrame minewFrame : advFrames) {
            	//每个帧的最近更新时间
                Log.v("beaconplus", "lastUpdate:" + minewFrame.getLastUpdate());
            }
        }
    }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

另外,你可以在任何时候停止扫描任务。

当前未处于扫描状态尝试停止扫描,返回“操作失败:当前非扫描状态,无需执行停止扫描操作。”

// 通过这种方式停止扫描
mtCentralManager.stopScan();
1
2

获取扫描状态

// true:扫描中,false:未扫描
mtCentralManager.isScanning();
1
2

SDK会缓存所有扫描到的设备并返回,如果需要清理缓存,需要先停止扫描再清理缓存。

当前正在扫描时尝试清除缓存,返回”操作失败:当前处于扫描状态,无法清除缓存,请先停止扫描。”

下拉刷新的代码示例:

// 停止扫描
mtCentralManager.stopScan();
// 清理缓存
mtCentralManager.clear();
// 开启扫描
mtCentralManager.startScan();
1
2
3
4
5
6

通过以上方式我们就可以获取到设备在广播阶段的所有相关数据,进一步的,想要从frameHandler里获取到所有的数据帧,可以参考以下代码示例:


/*
 * mtFrameHandler: MTFrameHandler实例,
 */
ArrayList<MinewFrame> advFrames = mtFrameHandler.getAdvFrames();
for (MinewFrame minewFrame : advFrames) {
    FrameType frameType = minewFrame.getFrameType();
    switch (frameType) {
        case FrameiBeacon://iBeacon
            IBeaconFrame iBeaconFrame = (IBeaconFrame) minewFrame;
            Log.v("beaconplus", iBeaconFrame.getUuid() + iBeaconFrame.getMajor() + 	iBeaconFrame.getMinor());
            break;
        case FrameUID://uid
            UidFrame uidFrame = (UidFrame) minewFrame;
            Log.v("beaconplus", uidFrame.getNamespaceId() + uidFrame.getInstanceId()) + 
                uidFrame.getTxPower();
            break;
        case FrameAccSensor://acc
            AccFrame accFrame = (AccFrame) minewFrame;
            Log.v("beaconplus", accFrame.getXAxis() + accFrame.getYAxis() + accFrame.getZAxis());
            break;
        case FrameHTSensor://ht
            HTFrame htFrame = (HTFrame) minewFrame;
            Log.v("beaconplus", htFrame.getTemperature() + htFrame.getHumidity());
            break;
        case FrameTLM://tlm
            TlmFrame tlmFrame = (TlmFrame) minewFrame;
            Log.v("beaconplus", tlmFrame.getTemperature() + tlmFrame.getBatteryVol() + tlmFrame.getSecCount() + tlmFrame.getAdvCount());
            break;
        case FrameURL://url
            UrlFrame urlFrame = (UrlFrame) minewFrame;
            Log.v("beaconplus", "Link:" + urlFrame.getUrlString() + "Rssi @ 0m:" + urlFrame.getTxPower());
            break;  
        case FrameLightSensor://light
            LightFrame lightFrame = (LightFrame) minewFrame;
            Log.v("beaconplus", "battery:" + lightFrame.getBattery() + "%" + lightFrame.getLuxValue());
            break; 
        case FrameForceSensor://force
            ForceFrame forceFrame = ((ForceFrame) minewFrame);
            Log.v("beaconplus", "battery:" + forceFrame.getBattery() + "%" + "force:" +  forceFrame.getForce() + "gram");
            break;
        case FramePIRSensor://pir
            PIRFrame pirFrame = ((PIRFrame) minewFrame);
            Log.v("beaconplus", "battery:" + pirFrame.getBattery() + "%" + "PIR:", pirFrame.getValue());
            break;
       case FrameTempSensor://temp
            TemperatureFrame temperatureFrame = (TemperatureFrame) minewFrame;
            Log.v("beaconplus", "battery:" + temperatureFrame.getBattery() + "%,temperature:" + String.format("%.2f", temperatureFrame.getValue()) + "°C");
            break;
       case FrameTVOCSensor://tvoc                  
            TvocFrame tvocFrame = (TvocFrame) minewFrame;
            Log.v("beaconplus", "battery:" + tvocFrame.getBattery() + ",TVOC:" tvocFrame.getValue() + "ppb," + "battery:" +tvocFrame.getBattery() + "mV");
            break;
       case FrameLineBeacon://FrameLineBeacon
            FrameLineBeacon lineBeacon = ((FrameLineBeacon) minewFrame);
            Log.v("beaconplus", "Hwid:" + lineBeacon.getHwid() 
                  + ",Rssi@1m:", lineBeacon.getTxPower() 
                  + ",authentication:"lineBeacon.getAuthentication() 
                  + ",timesTamp:" + lineBeacon.getTimesTamp());
            break;
       case FrameTamperProof://TamperProofFrame
            TamperProofFrame tamperProofFrame = ((TamperProofFrame) minewFrame);//
            Log.v("beaconplus", "battery:" + tamperProofFrame.getBattery());
            break;
       case FrameInfo://InfoFrame
            InfoFrame infoFrame = ((InfoFrame) minewFrame);//
            Log.v("beaconplus", infoFrame.getMajor() + infoFrame.getMinor() + 	infoFrame.getBatteryVoltage());
            break;     
       case FrameGInfo://GInfoFrame
            GInfoFrame GinfoFrame = ((GInfoFrame) minewFrame);//
            Log.v("beaconplus", GinfoFrame.getMac() + GinfoFrame.getBattery() + 	GinfoFrame.getUuid());
            break;   
       default:
			break;                  
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76

**重要提示:**在获取所有的传感器数据时,如果值等于MIN_VALUE(各基本类型封装类的最小值),表示这个数据是无效的。所有的传感器实例(HTFrame、LightFrame、AccFrame、ForceFrame、PIRFrame)都有这种情况。请参考以下代码:

/*
 * htFrame: HTFrame实例
 * lightFrame:LightFrame实例
 * accFrame:AccFrame实例
 * forceFrame:ForceFrame实例
 * pirFrame:PIRFrame实例
 */

if (htFrame.getTemperature == Double.MIN_VALUE) {
    Log.v("tag",this value is not available.");
}

if (lightFrame.getLuxValue == Integer.MIN_VALUE) {
    Log.v("tag",this value is not available.");
}

if (accFrame.getXAxis() == Double.MIN_VALUE) {
	Log.v("tag",this value is not available.");
}

if (forceFrame.getXAxis() == Double.MIN_VALUE) {
	Log.v("tag",this value is not available.");
}

if (pirFrame.getXAxis() == Double.MIN_VALUE) {
	Log.v("tag",this value is not available.");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 连接设备

要对设备进一步操作(修改参数,OTA),需要先连接到设备。由于有些设备在连接时可能需要密码验证,所以请注意回调状态PASSWORDVALIDATING:

/*
  mtCentralManager: MTCentralManager的单例
  mtPeripheral: MTPeripheral实例
*/
mtCentralManager.connect(mtPeripheral, connectionStatueListener);

// 整个连接过程需要经过多个中间状态,如果遇到错误,会通过error抛出
// 只有当status == COMPLETED时,才算是连接成功
private ConnectionStatueListener connectionStatueListener = new ConnectionStatueListener() {
    @Override
    public void onUpdateConnectionStatus(final ConnectionStatus connectionStatus, final GetPasswordListener getPasswordListener) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                switch (connectionStatus) {
                       	case CONNECTING:
                            Log.e("minew_tag", ": CONNECTING")
                            break;
                        case CONNECTED:
                           	Log.e("minew_tag", ": CONNECTED")
                            break;
                        case CONNECTED:
                           	Log.e("minew_tag", ": CONNECTED")
                            break;
                        case READINGINFO://先进行使能通知,在读取固件信息
                            Log.e("minew_tag", ": 读取设备固件信息")
                            break;
                       case DEVICEVALIDATING://校验设备成功后,向设备写入校验key
                            LogUtils.e("DEVICEVALIDATING");
                            break;   
                        //写入校验key后,如果需要连接密码,会回调这个状态
                        //密码长度必须为8个长度
                       case PASSWORDVALIDATING:
                        	String password = "minew123";
                        	getPasswordListener.getPassword(password);
                        	break;
                       case SYNCHRONIZINGTIME://密码校验完成
                         	Log.e("minew_tag", ": SYNCHRONIZINGTIME")
                            break;
                       case READINGCONNECTABLE:
                            Log.e("minew_tag", ": 读取设备固件信息")
                            LogUtils.e("READINGCONNECTABLE");
                            break;
                       case READINGFEATURE:
                            LogUtils.e("READINGFEATURE");
                            break;
                        case READINGFRAMES:
                            LogUtils.e("READINGFRAMES");
                            break;
                        case READINGTRIGGERS:
                            LogUtils.e("READINGTRIGGERS");
                            break;
                        case READINGSENSORS:
                            LogUtils.e("READINGSENSORS");
                            break;
                        case COMPLETED:
                        	//至此连接成功,并将参数写入了设备
                        	break;
                        
                }
            }
        });
    }

    @Override
    public void onError(MTException mtException) {

    }
};

// 断开某个设备的连接状态
mtCentralManager.disconnect(mtPeripheral);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72

# 获取基本信息

成功连接到设备后,就可以获取设备信息以及修改设备参数了。我们先来看看怎么获取各种基础数据。

/*
 * mtPeripheral: MTPeripheral实例
 */
MTConnectionHandler mtConnectionHandler = mtPeripheral.mMTConnectionHandler;
//当前的设备连接状态
ConnectState connectState = mtConnectionHandler.getConnectState();
// 连接时是否需要密码,None不需要,Require需要
PasswordState passwordState = mtConnectionHandler.getPasswordState();
// 设备信息,例如:(Firmware Version: 0.9.1);
HashMap<String, String> systeminfos = mtConnectionHandler.systeminfos;
String manufacturer = systeminfos.get(Constants.manufacturer);
String modlenumber = systeminfos.get(Constants.modlenumber);
String macAddress = systeminfos.get(Constants.serialnumber);
String hardware = systeminfos.get(Constants.hardware);
String firmware = systeminfos.get(Constants.firmware);
String software = systeminfos.get(Constants.software);

// 设备特性;
MTConnectionFeature mtConnectionFeature = mtConnectionHandler.mTConnectionFeature;
// 设备的广播通道数量,可以根据此动态生成UI;
int slotAtitude = mtConnectionFeature.getSlotAtitude();
// 可以调节参数类型:none不可调,adv可调,txpower可调,adv/txpower均可调
FeatureSupported featureSupported = mtConnectionFeature.getFeatureSupported();
// 支持的广播帧类型(多种)
List<FrameType> supportedSlots = mtConnectionFeature.getSupportedSlots();
// 支持的Txpower(多档位)
byte[] supportedTxpowers = mtConnectionFeature.getSupportedTxpowers();
//支持的Trigger(多种)
ArrayList<TriggerType> supportTriggers = mtConnectionFeature.supportTriggers;
// 固件版本枚举,方便处理版本号区分;
Version version = mtConnectionFeature.getVersion();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

PS: MTConnectionHandlerVersion属性说明:

Versoin值来自设备的固件版本号,可以通过检查Version的值来判断当前设备支持的功能:
 
// 初始化值 
UNDEFIND

// 基础版本,仅支持基础功能
VersionBase

// 此版本及以上支持自定义连接密码 / 设备信息广播帧
Version0_9_8

// 此版本及以上支持远程关机命令
Version0_9_9

// 此版本及以上开始支持触发器
Version2_0_0

// 此版本及以上开始支持带广播设置的触发器配置
Version2_2_60

//区别蓝牙4.2 和 5.0
VERSION2_5_X

//新版
VERSION2_NEW

// 理想值,支持所有特性。
VersionMax = 1000
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 获取各通道数据

接下来可以获取每个通道对应的参数数据,之所以单独讲这部分,是因为所有与修改相关的内容全部在这里。

在阅读代码之前,我们先来了解通道的概念:通道相当于一个广播工具,它不管里面是什么内容,只管把内容广播出来,例如一个6通道的设备可能是这样的:

通道序号 0 1 2 3 4 5
广播内容 iBeacon TLM UID URL HTSensor None
广播间隔(ms) 1000 2000 500 900 3000 0
RSSI@0/1m(dbm) -1 0 -4 -10 -3 0
信号强度(dbm) 4 0 -4 4 0 0

上表的意思是,第0个通道广播iBeacon数据,第1个通道广播TLM数据,…,第5个通道不广播任何数据。它们每个都有独立的广播间隔,信号强度和校准值,互相之间没有关系也不产生任何影响。

PS:校准值:RSSI@0/1m 指的是手机在设备0/1m(iBeacon为1,其它为0)处的信号强度。

接下来我们来看下怎么获取设备的通道数据。

/*
 * mtConnectionHandler: MTConnectionHandler实例
 */

// 当前设备每一个通道对应的数据帧
// 此数组的数量和通道数量保持一致(也就是mtConnectionFeature.getSlotAtitude())
// 按照通道顺序严格排序,比如:0. -> MinewiBeacon,1. -> MinewUID ...
ArrayList<MinewFrame> allFrames = mtConnectionHandler.allFrames;

/*
 * 假如第3个通道为iBeacon数据,第4个通道数据为UID,
 * 我们尝试解析第3个通道的数据。
 */

// 由于默认指针是MinewFrame(MinewiBeacon的父级)类型,需要做一下转换。

// 获取第3个通道的数据帧
MinewFrame minewFrame = allFrames.get(2);

// 确认此通道是iBeacon类型,如果类型确认,可以不需要做这一步判断
if(minewFrame.getFrameType()==FrameType.FrameiBeacon){
  IBeaconFrame iBeaconFrame = (IBeaconFrame) minewFrame;
  
  // iBeacon数据
  String uuid = iBeaconFrame.getUuid();
  int major = iBeaconFrame.getMajor();
  int minor = iBeaconFrame.getMinor();
  // 所处通道的参数
  int curSlot = iBeaconFrame.getCurSlot();// 第几通道
  int advInterval = iBeaconFrame.getAdvInterval();// 此通道的广播间隔
  int advtxPower = iBeaconFrame.getAdvtxPower();// 此通道的信号校准值 iBeacon:RSSI@1m others: RSSI@0m
  int radiotxPower = iBeaconFrame.getRadiotxPower();// 此通道信号强度
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

你可以使用同样的方式来解析其它所有通道,值得注意的是,当frameType == FrameNone 时,说明此通道没有广播数据。

# 修改通道数据

通过我们的开发套件,开发者可以自由的修改每个通道的广播数据。

我们区分了静态数据帧和动态数据帧,静态数据帧指的是广播数据不会发生变化的:iBeacon,UID,URL等等,动态数据帧指的是广播数据不定时发生变化的:TLM,各类传感器数据等。

可以通过下面的参考代码实现。

/*
 * mtConnectionHandler: MTConnectionHandler实例
 */

// 生成一个uid实例
UidFrame uidFrame = new UidFrame();
uidFrame.setFrameType(FrameType.FrameUID);
// 配置uid的namespaceId和instanceId参数
uidFrame.setNamespaceId("0123456789abdcdcba12");
uidFrame.setInstanceId("0123456789dc");
// 通道相关配置
uidFrame.setRadiotxPower(4);// 配置这个通道的广播功率
uidFrame.setAdvInterval(600);// 配置这个通道的广播间隔
uidFrame.setAdvtxPower(-3); // 配置这个通道的信号校准值 RSSI@0m

// 写入到设备
// 详情:1.把1通道设置为UID广播,namespaceId:0123456789abdcdcba12,instanceId:0123456789dc
//      2.设置1通道的广播间隔为600ms,RSSI@0m为-3dbm,广播功率为:4dbm
// 第二个参数为通道序号
 mtConnectionHandler.writeSlotFrame(uidFrame, 1, new MTCOperationCallback() {
     @Override
     public void onOperation(boolean success, MTException mtException) {
         if(success){
             Log.v("beaconplus","Success!");
         }else{
             Log.v("beaconplus",mtException.getMessage);
         }
     }
 });

//设置 LineaBeaconFrame
LineBeaconFrame lineBeaconFrame = new LineBeaconFrame();
lineBeaconFrame.setFrameType(FrameType.FrameLineBeacon);
String hwid = "123456abfc";//Hwid长度为 10,包含 a-f/A-F/0-9
String vendorKey = "123456ad";//VendorKey长度为 8,包含 a-f/A-F/0-9
Stirng lotKey = "123456";//LotKey长度为 16 包含 a-f/A-F/0-9
lineBeaconFrame.setHwid(hwid);
lineBeaconFrame.setVendorKey(vendorKey);
lineBeaconFrame.setLotKey(lotKey);
// 第二个参数为通道序号
 mtConnectionHandler.writeSlotFrame(lineBeaconFrame, 1, new MTCOperationCallback() {
     @Override
     public void onOperation(boolean success, MTException mtException) {
         if(success){
             Log.v("beaconplus","Success!");
         }else{
             Log.v("beaconplus",mtException.getMessage);
         }
     }
 });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

注意,Slot参数有范围:

  • Advertisement interval: 100 - 5000 (ms)
  • RSSI@0m: -127 - 0 (dbm)

注意**: Radio Txpower不是一个范围,它是几个特征值,如:-8,-4,0,4.你可以从“MTConnectionHandler”的feature属性获取所有支持的Radio Txpowers。

/*
 * mtConnectionFeature: a property of MTConnectionHandler.
 */

// get all supported Radio txpower of current device.
byte[] supportedTxpowers = mtConnectionFeature.getSupportedTxpowers();
1
2
3
4
5
6

你可以通过相同的方式修改任何一个通道的广播数据,如果需要关闭某个通道,只需要创建一个frameType=FrameNone的对象写入到设备即可,当然,别忘了设置通道序号。

# 特殊的帧数据

1.修改蓝牙广播方式:蓝牙5.0 125Kbps

   /**
     * 设置广播速率
     * @param mCurrentSlot 当前选择的通道值
     * @param templateBroadcastRate 1mbps设置为0, 125kbps设置为1
     * @return broadCastRate
     */
int broadCastRate = mtConnectionHandler.calculateBroadCastRate(int mCurrentSlot, int templateBroadcastRate);
mtConnectionHandler.setBroadcastRate(int broadCastRate, MTCOperationCallback mtcOperationCallback);// 设置广播速率

1
2
3
4
5
6
7
8
9

2.设置 Acc 帧参数:int odr, int wakeupThreshold, int wakeupDuration

//获取Acc 的源参数:int odr, int wakeupThreshold, int wakeupDuration
List<AccParamsModel> list = mtConnectionHandler.getSetAccParamsValue();
for(AccParamsModel paramsModel : list) {
    Pair<String, Integer> ordvalues = paramsModel.getOrdsVelues();
	Integer odr= ordvalues.second;
	int[] wakeupTimeValues = paramsModel.getWakeupTimeValues();//表示唤醒阈值范围
    int wakeupTime_min = wakeupTimeValuesp[0];//唤醒阈值最小值
    int wakeupTime_max = wakeupTimeValuesp[1];//唤醒阈值最大值
	int[] wakeupDurationValues = paramsModel.getWakeupDurationValues();//唤醒持续时间范围
    int wakeupDuration_min = wakeupTimeValuesp[0];//唤醒持续时间最小值
    int wakeupDuration_max = wakeupTimeValuesp[1];//唤醒持续时间最大值
}

/*
设置Acc 的参数:wakeupThreshold 在 int[]wakeupTimeValues 范围内取值,wakeupDuration在int[] wakeupDurationValues范围内取值
*/
mtConnectionHandler.setAccValue(MTCOperationCallback mtcOperationCallback, int odr, int wakeupThreshold, int wakeupDuration); 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 修改设备全局参数

设备全局参数指的是设备级的一些特性,比如:配置设备是否可连接,是否需要密码,恢复出厂等。

可以通过以下代码示例参考。

// 恢复出厂设置
mtConnectionHandler.resetFactorySetting(new MTCOperationCallback() {
    @Override
    public void onOperation(boolean success, MTException mtException) {
        if (success) {
            Log.v("beaconplus","Success!");
        } else {
            Log.v("beaconplus","Failed!");
        }
    }
});

// 更新设备可连接设置
// CONNECTABLE_YES为可连接,CONNECTABLE_NO不可连接,
// 配置设备不可连接后,可以通过按下设备上的按钮再次连接到设备。
// !!!:如果设备上没有按钮,且把设备配置为不可连后,设备将无法再次被连接。
mtConnectionHandler.updateConnectable(CONNECTABLE_NO, new MTCOperationCallback() {
    @Override
    public void onOperation(final boolean success, final MTException mtException) {
        if (success) {
            Log.v("beaconplus","Success!");
        } else {
            Log.v("beaconplus","Failed!");
        }
    }
});

// 修改/增加密码
// !!!: 只能设置8位密码,数字或英文字母;
// 设备没有密码时,会添加密码,设备有密码时,会更新密码。
mtConnectionHandler.modifyPassword("minew123", new MTCOperationCallback() {
    @Override
    public void onOperation(final boolean success, MTException mtException) {
        if (success) {
            Log.v("beaconplus","Success!");
        } else {
            Log.v("beaconplus","Failed!");
        }
    }
});
// 移除密码
mtConnectionHandler.removePassword(new MTCOperationCallback() {
    @Override
    public void onOperation(final boolean success, MTException mtException) {
        if (success) {
            Log.v("beaconplus","Success!");
        } else {
            Log.v("beaconplus","Failed!");
        }
    }
});

/**
 * 远程关闭设备
 * !!!:请确认设备上有按钮,可以通过按下按钮的方式重启,否则关闭后无法重启设备。
 * 
 * (新增)注意:如果model name为Beacon Plus-BL,设备不支持关机操作,将直接
 * 抛出UnsupportedOperationException异常。需要进行预先判断model name的值
 *
 * 通过MTConnectionHandler.systeminfos.get(Constants.modlenumber)获取model name的值
 */
mtConnectionHandler.powerOff(new MTCOperationCallback() {
    @Override
    public void onOperation(boolean success, MTException mtException) {
        if (success) {
            Log.v("beaconplus","Success!");
        } else {
            Log.v("beaconplus","Failed!");
        }
    }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71

# 设备上的传感器数据

BeaconPlus设备整合了温湿度,光感,加速度等多种传感器,目前我们提供了温湿度历史数据的读取接口和红外传感器参数设置接口。

**请注意:**在2019年1月9日,我们提供了新的接口来获取温湿度数据,如果使用的是在此日期后提供的新接口,请及时更换。代码如下:

  1. 红外传感器数据

请确认此设备带有温湿度传感器,否则无法正确获取数据。

可以通过以下代码示例参考。

/*
 *  mtConnectionHandler: MTConnectionHandler实例
 *  
 * long longtime = System.currentTimeMillis();
 * int time = (int) (longtime / 1000);
 * byte[] curTime = Tools.intToBytes(time)
 */

// 从设备上读取温湿度数据
mMTConnectionHandler.getMtSensorHandler().readSensorHistory(curTime, new MTSensorOperateCallback<SensorHTModel>() {
    @Override
    public void onResult(SensorHTModel sensorHTModel) {
        int date = sensorModel.getDate();// 此数据的Unix时间戳;
        float temperature = sensorModel.getTemperature();// 温度数据;
        float humidity = sensorModel.getHumidity();// 湿度数据;
    }

    @Override
    public void onNoHistoricalData() {
        Log.v("beaconplus","");
    }
});
// 从设备上读取温度数据
mMTConnectionHandler.getMtSensorHandler().readTempSensorHistory(curTime, new  MTSensorOperateCallback<SensorTempModel>() {
    @Override
    public void onResult(SensorTempModel sensorTempModel) {
        int date = sensorTempModel.getDate();// 此数据的Unix时间戳;
        float temperature = sensorModel.getTemperature();// 温度数据
    }
});
// 从设备上六轴传感器数据
mMTConnectionHandler.getMtSensorHandler().readLSMHistory(Tools.intToBytes(time),
                new MTSensorOperateCallback<SensorSixAxisModel>() {
                    @Override
                    public void onResult(SensorSixAxisModel sensorSixAxisModel) {
                        int date = sensorSixAxisModel.getDate();// 此数据的Unix时间戳;
                        double A_XAxis = sensorSixAxisModel.getA_XAxis();
                        double A_YAxis = sensorSixAxisModel.getA_YAxis();
                        double A_ZAxis = sensorSixAxisModel.getA_ZAxis();
                        double D_XAxis = sensorSixAxisModel.getD_XAxis();
                        double D_YAxis = sensorSixAxisModel.getD_YAxis();
                        double D_ZAxis = sensorSixAxisModel.getD_ZAxis();
                    }
                });
// 从设备上磁力计传感器数据
mMTConnectionHandler.getMtSensorHandler().readLISHistory(Tools.intToBytes(time),
                new MTSensorOperateCallback<SensorMAGModel>() {
                    @Override
                    public void onResult(SensorMAGModel sensorMAGModel) {
                        int date = sensorMAGModel.getDate();// 此数据的Unix时间戳;
                    	double A_XAxis = sensorMAGModel.getA_XAxis();
                        double A_YAxis = sensorMAGModel.getA_YAxis();
                        double A_ZAxis = sensorMAGModel.getA_ZAxis();
                    }
                });
// 从设备上大气压力传感器数据
mMTConnectionHandler.getMtSensorHandler().readDPSHistory(Tools.intToBytes(time),
                new MTSensorOperateCallback<SensorAPModel>() {
                    @Override
                    public void onResult(SensorAPModel sensorAPModel) {
                        	int date = sensorAPModel.getDate();// 此数据的Unix时间戳;
                    	    double pressure = sensorAPModel.getPressure();
                    }
                });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

你可以用表格或者曲线的方式把这些数据生动的呈现出来。

  1. 红外传感器数据

请确认此设备带有红外传感器,否则无法正确获取数据。可以通过以下代码示例参考:

/*
 *  mtConnectionHandler: MTConnectionHandler实例
 */
MTSensorHandler mtSensorHandler = mMtConnectionHandler.getMtSensorHandler();
Map<String, MTSensorModel> sensorMap = mtSensorHandler.getSensorMap();
SensorPIRModel sensorPIRModel = (SensorPIRModel) sensorMap.get(FrameType.FramePIRSensor.name());
//触发时间
int time = sensorPIRModel.getTime();
//是否允许重复触发
boolean repeatTrigger = sensorPIRModel.isRepeatTrigger();
1
2
3
4
5
6
7
8
9
10
# 配置触发器

BeaconPlus设备加入了触发器功能,你可以给每个通道独立的配置触发器,仅当满足触发条件时设备才会开启对应通道进行广播,否则,对应通道将会一直处于关闭状态。

可以通过以下代码示例参考。

//设置通道
private void SetPassage(int slot){
    Trigger trigger = new Trigger();//实例化触发器
    //一般是有 6个通道
	 swith(slot) {
        case 0://表示通道 1  
        trigger.setCurSlot(0);
        break;
        case 1://表示通道 2
        trigger.setCurSlot(1);
        break;
        case 2://表示通道 3
        trigger.setCurSlot(2);
        break;
        case 3://表示通道 4
        trigger.setCurSlot(3);
        break;
        case 4://表示通道 5
        trigger.setCurSlot(4);
        break;
        case 5://表示通道 6
        trigger.setCurSlot(5);
        break;
        defaultbreak
    }
    
    switch(TriggerType triggerType) {//这里的参数是一个枚举
        case TEMPERATURE_ABOVE_ALARM://温度高于
            trigger.setTriggerType(TEMPERATURE_ABOVE_ALARM);//触发条件:温度高于
            break;
        case TEMPERATURE_BELOW_ALARM://温度低于
            trigger.setTriggerType(TEMPERATURE_BELOW_ALARM);
            break;
        case HUMIDITY_ABOVE_ALRM://湿度高于
            trigger.setTriggerType(HUMIDITY_ABOVE_ALRM);
            break;
        case HUMIDITY_BELOW_ALRM://湿度低于
            trigger.setTriggerType(HUMIDITY_BELOW_ALRM);
            break;
        case BTN_DTAP_EVT://双击
            trigger.setTriggerType(BTN_DTAP_EVT);
            break;
        case BTN_TTAP_EVT://三击
            trigger.setTriggerType(BTN_TTAP_EVT);
            break;
    }

    
int mTemCondition= 10;//达到触发条件后,广播时长:10s
switch (triggerType) {
                        case TEMPERATURE_ABOVE_ALARM://温度高于
                        case TEMPERATURE_BELOW_ALARM://温度低于
                        case HUMIDITY_ABOVE_ALRM://湿度高于
                        case HUMIDITY_BELOW_ALRM://湿度低于
                        case LIGHT_ABOVE_ALRM://光感高于
                        case LIGHT_BELOW_ALARM://光感低于
                        case FORCE_ABOVE_ALRM://压感高于
                        case FORCE_BELOW_ALRM://压感低于
                        case TVOC_ABOVE_ALARM://TVOC高于
                        case TVOC_BELOW_ALARM://TVOC低于
                            trigger.setCondition(mTemCondition);
                            break;
                        default:
                            trigger.setCondition(mTemCondition * 1000);
                    }
    
trigger.setAlwaysAdvertising(boolean alwaysAdvertising)//设置是否开启基础参数广播。true:开启,false:关闭。
trigger.setTriggerType(TriggerType.TRIGGER_SRC_NONE)//设置是否开启触发器广播。当值为TRIGGER_SRC_NONE时关闭,其它值时开启。

   
trigger.setAdvInterval(int advInterval)//广播的条件:时间间隔,最大值为 5000ms
trigger.setRadioTxpower(int radioTxpower)//广播条件:广播信号强度,最大为 4,一般这个是负值
mMTConnectionHandler.setTriggerCondition(trigger,new SetTriggerListener() {
    @Override
    public void onSetTrigger(boolean success, MTException mtException) {
        if (success) {
            Log.v("beaconplus","Success!");
        } else {
            Log.v("beaconplus","Failed!");
        }
    }
});
}

public enum TriggerType{
 MOTION_DETECT(0),//加速度
    TEMPERATURE_ABOVE_ALARM(1),//温度高于
    TEMPERATURE_BELOW_ALARM(2),//温度低于
    HUMIDITY_ABOVE_ALRM(3),//湿度高于
    HUMIDITY_BELOW_ALRM(4),//湿度低于
    LIGHT_ABOVE_ALRM(5),//光感高于
    BTN_PUSH_EVT(6),//按下按键
    BTN_RELEASE_EVT(7),//按键弹出
    BTN_STAP_EVT(8),//单击按键
    BTN_DTAP_EVT(9),//双击按键
    BTN_TTAP_EVT(10),//三击按键
    LIGHT_BELOW_ALARM(11),//光感低于
    FORCE_ABOVE_ALRM(12),//压感大于
    FORCE_BELOW_ALRM(13),//压感低于
    PIR_DETECT(14),//红外
    TVOC_ABOVE_ALARM(15),//TVOC大于
    TVOC_BELOW_ALARM(16),//TVOC低于
    VIBRATION_DETECT(17),//振动触发
    LEAKAGE_ALARM(18),//泄露报警

    TRIGGER_SRC_NONE(-1);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# 振动传感器相关操作
 /**
     * 设置振动传感器的灵敏度
     * 超高:0x00, 高:0x01,中:0x02,低:0x03,超低:0x04
     */
byte[] bytes = new byte[]{0x00};
mMTConnectionHandler.setVibrationSensitivity(bytes, new MTCOperationCallback() {
            @Override
            public void onOperation(boolean success, MTException mtException) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                    	
                    }
                });
            }
        });

/**
* 设置报警时区
* String startTime = "19:00" 
* String endTime = "21:00"
* int switch_status = 0 // 0:关,1:开
*/
mMTConnectionHandler.setAlarmTime(startTime1,endTime1,switch_status1,
startTime2,endTime2,switch_status2,
startTime3,endTime3,switch_status3,
mtcOperationCallback);

//设置报警时区的回调方法
MTCOperationCallback mtcOperationCallback = new MTCOperationCallback() {
        @Override
        public void onOperation(boolean success, MTException mtException) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {

                    }
                }
            });
        }
    };

/**
* 设置振动功能开关
* 0x01:开,0x00: 关
* byte[] statusByte = new byte[]{0x00}; 关
* byte[] statusByte = new byte[]{0x01};	开
*/
statusByte = new byte[]{0x00};
mMTConnectionHandler.setVibrationsensorStatus(statusByte, new MTCOperationCallback() {
          @Override
          public void onOperation(boolean success, MTException mtException) {
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                
                            }
                        });
                    }
                });


mMTConnectionHandler.inquireVibrationSensitivity(mtSensorOperateCallback);//获取振动灵敏度
mMTConnectionHandler.inquireVibrationsensorStatus(mtSensorOperateCallback);//查询振动状态
mMTConnectionHandler.inquireAlarmTime(mtSensorOperateCallback);//查询报警时区

 /**
  * 获取振动灵敏度,查询振动状态,查询报警时区的回调方法
  */
MTSensorOperateCallback mtSensorOperateCallback = new MTSensorOperateCallback() {
   @Override
   public void onResult(MTSensorModel mtSensorModel) {
   	if (mtSensorModel instanceof VibrationSensorModel) {
       VibrationSensorModel vibrationSensorModel = (VibrationSensorModel) mtSensorModel;

     } else if (mtSensorModel instanceof VibrationStatusModel) {
       VibrationStatusModel vibrationStatusModel = (VibrationStatusModel) mtSensorModel;
               
     } else if (mtSensorModel instanceof SensorTimeModel) {
          SensorTimeModel sensorTimeModel = (SensorTimeModel) mtSensorModel;
                        switch (sensorTimeModel.getTimeInterval()) {
                            case 1:
                                startTime1 = sensorTimeModel.getStartTime();
                                endTime1 = sensorTimeModel.getEndTime();
                                timeSwitch1 = sensorTimeModel.getTimeSwitch() ? 1 : 0;
                                break;
                            case 2:
                                startTime2 = sensorTimeModel.getStartTime();
                                endTime2 = sensorTimeModel.getEndTime();
                                timeSwitch2 = sensorTimeModel.getTimeSwitch() ? 1 : 0;
                                break;
                            case 3:
                                startTime3 = sensorTimeModel.getStartTime();
                                endTime3 = sensorTimeModel.getEndTime();
                                timeSwitch3 = sensorTimeModel.getTimeSwitch() ? 1 : 0;
                                break;
                            default:
                                break;
                        }
            }
        }
    };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102

现在,你可以参考以上示例进行开发了。

# 注意事项

  1. 在Android-6.0以上版本蓝牙扫描需要动态申请地理位置权限,具体如下:
  String[] requestPermissions;
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
          requestPermissions = new String[]{
                  Manifest.permission.BLUETOOTH_SCAN,
                  Manifest.permission.BLUETOOTH_CONNECT,
                  Manifest.permission.ACCESS_COARSE_LOCATION,
                  Manifest.permission.ACCESS_FINE_LOCATION
          };

      } else {
          requestPermissions = new String[]{
                  Manifest.permission.ACCESS_COARSE_LOCATION,
                  Manifest.permission.ACCESS_FINE_LOCATION
          };

      }
      ActivityCompat.requestPermissions(this,
              requestPermissions, PERMISSION_CODE);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  1. 由于设备的连接过程是放在Service中,所以建议在app启动后调用以下方法开启服务,否者连接功能将不能使用。具体如下:

    /*
     * mtCentralManager:MTCentralManager实例
     */
    MTCentralManager mtCentralManager = MTCentralManager.getInstance(this);
    mtCentralManager.startService();
    
    //断开服务
    mtCentralManager.stopService();
    
    1
    2
    3
    4
    5
    6
    7
    8

# 文档版本记录

  • 2020.10.27 v1.11 每个广播帧增加lastUpdate 属性;

  • 2020.3.25 v1.10 添加支持LineBeacon 帧的信息展示和数据设置;

  • 2019.12.04 v1.3 修改Android 9.0中部分手机在息屏状态下无法扫描的问题,增加过滤条件;

  • 2019.7.11 v1.2第三版,更改powerOff使用,具体前往查看;

  • 2019.1.9 v1.1第二版;

Stashed changes

  • 2017.10.13 v1.0 初版;
  • 2019.1.9 v1.1第二版;
  • 2019.7.11 v1.2第三版,更改powerOff使用,具体前往查看;
  • 2019.12.04 v1.3 修改Android 9.0中部分手机在息屏状态下无法扫描的问题,增加过滤条件;
  • 2020.3.25 v1.10 添加支持LineBeacon 帧的信息展示和数据设置;
  • 2020.10.27 v1.10.2 每个广播帧新增 lastUpdate 属性
  • 2021.4.21 v1.11.0 新增六轴/磁力计/大气压力传感器
  • 2021.4.28 v1.11.1 新增振动传感器
  • 2022.2.23 v1.15.1 新增防拆帧TamperProofFrame,修复优化sdk无法连接设备问题;
  • 2022.5.9 新增广播帧InfoFrame;
  • 2022.5.10 特殊的帧数据:支持蓝牙5.0 125kbps速率,支持 ACC 帧的参数修改;
  • 2022.5.23 新增GInfo广播帧的解析和参数修改;
  • 2024.1.2 添加设置基础参数广播和触发器广播开关的注释,增加TriggerType类型,增加设备和帧的最新更新时间获取方式说明;
  • 2024.1.16 添加清理缓存方法说明,修正一些方法的使用以及说明;
上次更新:: 2024/4/12 18:27:17