# MinewMTB13Kit 说明文档
本套 SDK 仅支持 Minew 公司出品的蓝牙设备。
通过 SDK 可以帮助开发者处理手机和蓝牙设备之间的一切工作,包括:扫描设备,广播数据、连接设备,向设备写入数据,从设备接收数据等。
目前 SDK 仅支持 MTB13工业高防护资产标签设备使用。
# 前期工作
整体框架:MTB13BleDevicesManager 为设备管理类,在 APP 运行时始终是单例。MTB13Entity 是设备实例类,此套件会为每一个设备生成一个实例,在扫描和连接后都会使用,内部包含设备广播数据,在扫描期间该数据会随着设备不停广播而更新。
MTB13BleDevicesManager :设备管理类,可以扫描周围的设备,并且可以连接它们,校验它们等
MTB13Entity :扫描时获取到的设备实例,继承自 BaseBleDeviceEntity
# 一.导入到工程
# 1. 开发环境
SDK 最低支持 Android 7.0,对应 API Level 为 24 。在 module 的 build.gradle 中设置 minSdkVersion 为 24 或 24 以上 :
android {
defaultConfig {
applicationId "com.xxx.xxx"
minSdkVersion 24
}
}
2
3
4
5
6
# 2. 添加库
将 aar 包添加到 module 的 libs 文件夹下,并在该 module 的 build.gradle 中添加如下语句(直接添加依赖):
implementation files('libs\\SDK_MTB13-release.aar')
api 'androidx.appcompat:appcompat:1.5.0'
api 'org.lucee:bcprov-jdk15on:1.52.0'
api 'com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.16.0'
api 'com.fasterxml.jackson.core:jackson-databind:2.15.0'
2
3
4
5
6
添加 .so 库文件,App 目录下 build.gradle 添加如下配置:
android {
defaultConfig {
ndk {
abiFilters 'armeabi-v7a','arm64-v8a','x86','x86_64'
}
}
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
}
2
3
4
5
6
7
8
9
10
11
12
# 3. 添加Ble相关权限
在AndroidManifest.xml需要以下权限,如果targetSdkVersion大于23,则需要做权限管理以获取权限:
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" tools:node="replace" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" tools:node="replace" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" tools:remove="android:usesPermissionFlags" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" tools:remove="android:usesPermissionFlags" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" tools:remove="android:usesPermissionFlags" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
2
3
4
5
6
7
8
9
10
# 二.使用
SDK 分为扫描、连接和读写三个阶段。
# 1开始扫描
Android6.0 系统以上,进行 BLE 扫描时,需要先申请到蓝牙权限后并且打开定位开关才能进行。
开启蓝牙扫描需要首先打开蓝牙,如果未打开蓝牙就去扫描,APP 会闪退。可通过 BLETool.checkBluetooth(this) 来判断蓝牙是否已经打开。如果没有打开,可以先打开蓝牙。
private fun checkBlePermissions() {
val requestPermissionList = listOf(
Manifest.permission.BLUETOOTH_SCAN,
Manifest.permission.BLUETOOTH_CONNECT,
Manifest.permission.BLUETOOTH_ADVERTISE,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION
)
PermissionConverter.putPermissionDescriptionMap(
R.string.common_permission_nearby_devices,
R.string.common_request_gps_nearby_devices_message_permission
)
PermissionConverter.putPermissionDescriptionMap(
R.string.common_permission_location,
R.string.common_request_gps_message_permission
)
if (!XXPermissions.isGrantedPermissions(this,requestPermissionList)){
XXPermissions.with(this)
.permission(requestPermissionList)
.interceptor(PermissionInterceptor())
.description(PermissionDescription())
.request(object : OnPermissionCallback {
override fun onGranted(
permissions: MutableList<String>,
allGranted: Boolean
) {
if (!allGranted) {
return
}else{
checkBluetooth()
}
}
})
}else{
checkBluetooth()
}
}
fun checkBluetooth(){
when (BLETool.checkBluetooth(this@ScanMtb13DevicesActivity)) {
BluetoothState.BLE_NOT_SUPPORT -> {
Toast.makeText(this@ScanMtb13DevicesActivity, "Not Support BLE", Toast.LENGTH_SHORT).show()
}
BluetoothState.BLUETOOTH_OFF -> {
val enableIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
launcherActivityResultForBle.launch(enableIntent)
}
BluetoothState.BLUETOOTH_ON -> {
startScan()
}
else -> {
}
}
}
/**
* 开启扫描
*/
fun startScan(){
val manager = MTB13BleDevicesManager.getInstance()
manager.startScan(ModuleMTB13Application.getApplication().mApplication, 5*60*1000,object :
OnScanDevicesResultListener<MTB13Entity> {
override fun onScanResult(scanList: MutableList<MTB13Entity>) {
}
override fun onStopScan(scanList: MutableList<MTB13Entity>) {
}
})
}
/**
* 停止扫描
*/
private fun stopScan() {
val manager = MTB13BleDevicesManager.getInstance()
manager.stopScan(ModuleMTB13Application.getApplication().mApplication)
}
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
注意:在扫描期间,不要频繁的调用manager.startScan()和manager.stopScan()方法。请设置合理的扫描持续时间。1分钟内避免多次调用manager.startScan()和manager.stopScan()方法时蓝牙协议栈出问题。如果出现问题,请前往手机系统设备->蓝牙开关->手动点击关闭再开启。
在扫描期间 APP 能够通过 sdk 获取到设备当前的一部分数据。如下所示通过 MTB13Entity 获取设备数据,该数据保存在广播帧对象中。
SDK 提供了 BaseBleDeviceEntity 作为 MTB13Entity 的基类,用于存储设备的公有数据,如下表所示:
| 名称 | 类型 | 说明 |
|---|---|---|
| macAddress | String | 设备mac |
| name | String | 设备名称 |
| rssi | int | 信号强度 |
BaseBleDeviceEntity 还保存了一个 BaseNanoLinkFrame,内部用于存储其在扫描期间获取到的设备广播数据帧,可通过如下方式取出:
val mtb13AdvFrame : Mtb13AdvFrame? = item.nanoLinkFrame?.let {it as Mtb13AdvFrame}
//mac 地址
val mac = mtb13AdvFrame?.macAddress
//deviceName 设备名称
String deviceName = mtb13AdvFrame?.deviceName
//battery 电量百分比,默认:Integer.MIN_VALUE 表示未获取到电量
val battery = mtb13AdvFrame?.battery
//固件版本号
val firmwareVersion = mtb13AdvFrame?.firmwareVersion
//温度数据
mtb13AdvFrame?.temperatureHumidityList?.let { htList ->
val temperature = htList[0].temperature
}
//运动状态
mtb13AdvFrame?.motionState?.let { motion ->
val state = motion.motionState
}
//门窗状态
mtb13AdvFrame?.motionState?.let { contactSensorState ->
val state = contactSensorState.state
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
MTB13工业高防护资产标签设备有 1 种广播帧类型。
设备帧
- Mtb13AdvFrame
| 名称 | 类型 | 说明 |
|---|---|---|
| mac | String | 固件mac |
| deviceName | String | 设备名称 |
| firmwareVersion | String | 版本号 |
| battery | int | 电量百分比,默认:Integer.MIN_VALUE 表示未获取到电量 |
| motionState | MotionState | Acc运动状态,motionState=0静止,motionState=1运动 |
| contactSensorState | ContactSensorState | 门窗状态 contactSensorState =0关,contactSensorState=1开 |
| temperatureHumidityList | List | 温度数据 temperature=-128f 无效值。其他则是有效值 |
# 2连接
连接前一般需要先停止扫描 ,SDK 提供了连接和断开连接方法。
val mBleManager = MTB13BleDevicesManager.getInstance()
//停止扫描
mBleManager.stopScan(context);
//连接 : module 为准备连接的设备
val module:MTB13Entity;
mBleManager.connect(context,module)
//断开连接 :macAddress 为设备 mac
mBleManager.disConnect(macAddress)
2
3
4
5
6
7
8
注意:连接设备前,请确认是否扫描到设备,如果没扫描到设备广播,调用连接方法,将连接失败。
在调用 connect() 后,SDK 中会对连接过程会有状态监听。
//设置监听器
mBleManager.setOnConnStateListener {macAddress, connectionState ->
when (it) {
BleConnectionState.Connecting -> {
//调用connect()后就会回调该状态
}
BleConnectionState.Connected -> {
//初步连接成功,作为一个过渡阶段,此时并未真正成功
}
BleConnectionState.EnterAuthenticatePassword -> {
}
BleConnectionState.AuthenticateSuccess ->{
}
BleConnectionState.AuthenticateFail ->{
}
BleConnectionState.ConnectComplete -> {
//设备连接完成,才能通过 MTB13BleDevicesManager 中提供的方法操作设备或者跳转界面
}
BleConnectionState.Bond_None -> {
//设备配对回调,此处为无效配对状态
}
BleConnectionState.Bond_Bonding -> {
//设备配对回调,此处为正在配对状态
}
BleConnectionState.Bond_Bonded -> {
//设备配对回调,此处为配对完成状态
//设备配对完成,才能通过 Msg01BleDevicesManager 中提供的方法操作设备
}
BleConnectionState.Disconnect -> {
LogUtil.d("connectionListener", "ConnectionState.Disconnect")
LoadingDialogUtil.dismissLoadingDialog()
// ToastUtils.showLong(getString(R.string.conn_failure))
}
else -> {}
}
}
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
在连接过程中,sdk会返回多个连接状态到app中,app需要做好处理。
- BleConnectionState.Connecting,BleConnectionState.Connected: 连接设备中,在这里状态下不要做耗时操作,因为此时在连接设备发现服务,并发送认证数据等。
- BleConnectionState.Bond_None: 设备配对无效状态。
- BleConnectionState.Bond_Bonding: 设备正在配对状态。
- BleConnectionState.Bond_Bonded: 设备配对完成状态,才能通过 Msg01BleDevicesManager中提供的方法操作设备。
- BleConnectionState.ConnectComplete: 设备已经连接成功,可以进行读写操作,比如配置广播参数、读取历史数据等。
- BleConnectionState.Disconnect: 连接失败或者设备断开连接会回调。
# 3设备配置读取写入操作
设备配置读取写入操作 API 如下,使用 MTB13BleDevicesManager.getInstance() 对象调用完成:
# 1. 给设备授予手机当前时间
默认SDK在连接设备阶段会自动给设备授予手机时间
// 首先要检查蓝牙开关是否打开,只有在打开的情况下才能开启手机的 CurrentTimeService 服务,如果蓝牙开关关闭去开启,部分 Android 手机系统可能会出错。并且需要监听蓝牙开关状态,当蓝牙开关开启时候需要开启服务 startSyncTimeServer,关闭的时候需要关闭服务 closeSyncTimeServer。注意,当 activity 页面 ondestroy 时,也需要关闭该服务 closeSyncTimeServer
val mBleManager = MTB13BleDevicesManager.getInstance()
// 开启服务
mBleManager.startSyncTimeServer(context)
// 关闭服务
mBleManager.closeSyncTimeServer(context)
// 因为部分手机会偶现授时失败的情况,所以需要判断授时是否成功,建议在设备连接成功 3 秒后判断
mBleManager.isDeviceReadSyncTime(context);
// 如果授时操作不成功,那么需要主动给设备发送授时指令
mBleManager.writeTimeToDevice(macAddress, onWriteTimeToDeviceListener);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 2. 获取固件版本
FirmwareVersionModel
| 名称 | 类型 | 说明 |
|---|---|---|
| versionInfoList | List | 版本信息集合 |
VersionInfo
| 名称 | 类型 | 说明 |
|---|---|---|
| firmwareName | String | 固件名称 |
| firmwareType | int | 默认值-1 |
| firmwareVersion | String | 固件版本号 |
| slot | String | 插槽, |
| methods | ArrayList |
/**
* 获取固件版本
* @param macAddress 设备mac
* @param listener 监听器
*/
void getFirmwareVersion(@NonNull String macAddress, OnQueryResultListener<FirmwareVersionModel> listener);
/**
* 获取固件版本信息
* @return FirmwareVersionModel?
*/
suspend fun getFirmwareInfo(): FirmwareVersionModel? = withContext(Dispatchers.Default){
if(connectMacAddress == null){
return@withContext null
}
return@withContext suspendCancellableCoroutine<FirmwareVersionModel?> { continuation ->
manager.getFirmwareVersion(connectMacAddress!!,
OnQueryResultListener<FirmwareVersionModel> { _, queryInfo ->
continuation.resume(queryInfo, null)
})
}
}
/**
* 获取版本信息
*/
private fun getFirmwareInfo(){
lifecycleScope.launch(Dispatchers.Main){
LoadingDialogUtil.showLoadingDialog()
val result = mConnectedViewModel.getFirmwareInfo()
LoadingDialogUtil.dismissLoadingDialog()
result?.let {
val firmwareVersion = it.versionInfoList.first()
binding.tvFirmwareVersion.text = "V${firmwareVersion.firmwareVersion}"
}
}
}
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
# 3. 恢复出厂设置
/**
* String macAddress
* 恢复出厂设置
*
* @param macAddress 设备mac
* @param listener 监听器
*/
void reset(@NonNull String macAddress, OnModifyConfigurationListener listener);
/**
* 恢复出厂设置
* @return Boolean
*/
suspend fun reset(): Boolean = withContext(Dispatchers.Default) {
if (connectMacAddress == null) {
return@withContext false
}
return@withContext suspendCancellableCoroutine<Boolean> { continuation ->
manager.reset(connectMacAddress!!) {
continuation.resume(it, null)
}
}
}
/**
* 恢复出厂设置
*/
private fun reset(){
lifecycleScope.launch(Dispatchers.Main){
LoadingDialogUtil.showLoadingDialog()
val result = mConnectedViewModel.reset()
LoadingDialogUtil.dismissLoadingDialog()
ToastUtils.showShort(when(result){
true -> R.string.common_reset_success
else -> R.string.common_reset_failure
})
mConnectedViewModel.disconnect(mConnectedViewModel.connectMacAddress!!)
finish()
}
}
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
# 4. 关机
/**
* 关机
*
* @param macAddress 设备mac
* @param listener 监听器
*/
void powerOff(@NonNull String macAddress, OnModifyConfigurationListener listener);
/**
* 关机
* @return Boolean
*/
suspend fun shutdown(): Boolean = withContext(Dispatchers.Default) {
if (connectMacAddress == null) {
return@withContext false
}
return@withContext suspendCancellableCoroutine<Boolean> { continuation ->
manager.powerOff(connectMacAddress!!) {
continuation.resume(it, null)
}
}
}
/**
* 关机
*/
private fun powerOff(){
lifecycleScope.launch(Dispatchers.Main){
LoadingDialogUtil.showLoadingDialog()
val result = mConnectedViewModel.shutdown()
LoadingDialogUtil.dismissLoadingDialog()
ToastUtils.showShort(when(result){
true -> R.string.common_power_off_success
else -> R.string.common_power_off_failure
})
mConnectedViewModel.disconnect(mConnectedViewModel.connectMacAddress!!)
finish()
}
}
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
# 5.重启
/**
* 重启
* @param macAddress 设备mac
*/
void reboot( String macAddress, OnModifyConfigurationListener listener);
/**
* 重启
* @return Boolean
*/
suspend fun reboot(): Boolean = withContext(Dispatchers.Default) {
if (connectMacAddress == null) {
return@withContext false
}
return@withContext suspendCancellableCoroutine<Boolean> { continuation ->
manager.reboot(connectMacAddress!!) {
continuation.resume(it, null)
}
}
}
/**
* 重启设备
*/
private fun reboot(){
lifecycleScope.launch(Dispatchers.Main){
LoadingDialogUtil.showLoadingDialog()
val result = mConnectedViewModel.reboot()
LoadingDialogUtil.dismissLoadingDialog()
ToastUtils.showShort(when(result){
true -> R.string.common_reboot_success
else -> R.string.common_reboot_failure
})
mConnectedViewModel.disconnect(mConnectedViewModel.connectMacAddress!!)
finish()
}
}
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
# 6. 设置静态密码
/**
* 设置静态密码
* @param macAddress mac地址
* @param passkey 密码 6-digit and the range is 000000 - 999999
* @param listener 监听器
*/
void setStaticPassKey(@NonNull String macAddress, int passkey, OnModifyConfigurationListener listener);
/**
* 修改设备密码
* @return Boolean
*/
suspend fun setPasskey(password:Int): Boolean = withContext(Dispatchers.Default) {
if (connectMacAddress == null) {
return@withContext false
}
return@withContext suspendCancellableCoroutine<Boolean> { continuation ->
manager.setStaticPassKey(connectMacAddress!!,password) {
continuation.resume(it, null)
}
}
}
/**
* 设置设备密码
* @param password String
*/
private fun setDevicePassword(password:Int){
lifecycleScope.launch(Dispatchers.Main) {
LoadingDialogUtil.showLoadingDialog()
val result = mConnectedViewModel.setPasskey(password)
LoadingDialogUtil.dismissLoadingDialog()
ToastUtils.showShort(when(result){
true -> R.string.common_set_device_password_success_message
else -> R.string.common_set_device_password_fail_message
})
}
}
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
# 7.查询设置设备名称
/**
* 获取设备名称
* @param macAddress mac地址
* @param listener 监听器
*/
void getDeviceName( String macAddress, OnQueryResultListener<String> listener);
/**
* 设置设备名称
* @param macAddress mac地址
* @param name 设备名称 最大长度9个字符
* @param listener 监听器
*/
void setDeviceName( String macAddress, String name, OnModifyConfigurationListener listener);
/**
* 获取设备名称
* @return String?
*/
suspend fun getDeviceName(): String? = withContext(Dispatchers.Default){
if(connectMacAddress == null){
return@withContext null
}
return@withContext suspendCancellableCoroutine<String?> { continuation ->
manager.getDeviceName(connectMacAddress!!,{ _, name ->
continuation.resume(name, null)
})
}
}
/**
* 设置设备名称
* @return String?
*/
suspend fun setDeviceName(name:String):Boolean = withContext(Dispatchers.Default){
if(connectMacAddress == null){
return@withContext false
}
return@withContext suspendCancellableCoroutine<Boolean> { continuation ->
manager.setDeviceName(connectMacAddress!!,name) {
continuation.resume(it, null)
}
}
}
/**
* 获取设备名称
*/
private fun getDeviceName(){
lifecycleScope.launch(Dispatchers.Main){
LoadingDialogUtil.showLoadingDialog()
val result = mConnectedViewModel.getDeviceName()
LoadingDialogUtil.dismissLoadingDialog()
result?.let {
binding.tvDeviceName.text = it
}
}
}
/**
* 设置设备名 name长度限制9个字符以内
*/
private fun setDeviceName(name:String){
lifecycleScope.launch(Dispatchers.Main){
LoadingDialogUtil.showLoadingDialog()
val result = mConnectedViewModel.setDeviceName(name)
LoadingDialogUtil.dismissLoadingDialog()
ToastUtils.showShort(when(result){
true -> R.string.common_set_device_name_success_message
else -> R.string.common_set_device_name_fail_message
})
if(result){
binding.tvDeviceName.text = name
}
}
}
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
# 8. 查询ht传感器参数
TemperatureHumidityConfiguration
| 名称 | 类型 | 描述 |
|---|---|---|
| intvl | int | 温湿度采样间隔。单位秒 |
/**
* 查询ht传感器参数
* @param macAddress
* @param listener
*/
void getTemperatureHumidityConfig(String macAddress, OnQueryResultListener<TemperatureHumidityConfiguration> listener);
/**
* 设置ht传感器参数
* @param macAddress
* @param htSensorConfig
* @param listener
*/
void setTemperatureHumidityConfig(String macAddress, TemperatureHumidityConfiguration temperatureHumidityConfiguration, OnModifyConfigurationListener listener);
/**
* 获取温湿度参数
* @return AdvConfigEntity?
*/
suspend fun getTemperatureHumidityConfig(): TemperatureHumidityConfiguration? = withContext(Dispatchers.Default){
if(connectMacAddress == null){
return@withContext null
}
return@withContext suspendCancellableCoroutine<TemperatureHumidityConfiguration?> { continuation ->
manager.getTemperatureHumidityConfig(connectMacAddress!!,
OnQueryResultListener<TemperatureHumidityConfiguration> { _, queryInfo ->
continuation.resume(queryInfo, null)
})
}
}
/**
* 设置温湿度参数
* @return Boolean
*/
suspend fun setTemperatureHumidityConfig(temperatureHumidityConfiguration: TemperatureHumidityConfiguration): Boolean = withContext(Dispatchers.Default) {
if (connectMacAddress == null) {
return@withContext false
}
return@withContext suspendCancellableCoroutine<Boolean> { continuation ->
manager.setTemperatureHumidityConfig(connectMacAddress!!,temperatureHumidityConfiguration) {
continuation.resume(it, null)
}
}
}
/**
* 获取温湿度配置参数
*/
private fun getTemperatureHumidityConfig(){
lifecycleScope.launch(Dispatchers.Main){
LoadingDialogUtil.showLoadingDialog()
val result = mConnectedViewModel.getTemperatureHumidityConfig()
LoadingDialogUtil.dismissLoadingDialog()
result?.let {
binding.htSamplingIntervalTv.text = "${DataUtil.getHour(it.intvl)}h ${DataUtil.getMinute(it.intvl)}m ${DataUtil.getSecond(it.intvl)}s"
}
}
}
/**
* 设置温湿度配置参数
*/
private fun setTemperatureHumidityConfig(temperatureHumidityConfiguration: TemperatureHumidityConfiguration){
lifecycleScope.launch(Dispatchers.Main){
LoadingDialogUtil.showLoadingDialog()
val result = mConnectedViewModel.setTemperatureHumidityConfig(temperatureHumidityConfiguration)
LoadingDialogUtil.dismissLoadingDialog()
when(result){
true -> ToastUtils.showShort(getString(R.string.common_config_success))
else -> ToastUtils.showShort(getString(R.string.common_config_fail))
}
}
}
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
# 9.查询和设置ACC
AccSensorConfiguration
| 名称 | 类型 | 描述 |
|---|---|---|
| sens | int | 触发灵敏度,档位:1,2,3,4. 数值越大越灵敏 |
| window | int | 检测时长。档位:200,500,1000,2000. |
/**
* 查询Acc传感器参数
* @param macAddress
* @param listener
*/
void getAccSensorConfig(String macAddress, OnQueryResultListener<AccSensorConfiguration> listener);
/**
* 查询Acc传感器参数
* @param macAddress
* @param listener
*/
void setAccSensorConfig(String macAddress, AccSensorConfiguration accSensorConfiguration, OnModifyConfigurationListener listener);
/**
* 获取Acc传感器参数
* @return AdvConfigEntity?
*/
suspend fun getAccConfig(): AccSensorConfiguration? = withContext(Dispatchers.Default){
if(connectMacAddress == null){
return@withContext null
}
return@withContext suspendCancellableCoroutine<AccSensorConfiguration?> { continuation ->
manager.getAccSensorConfig(connectMacAddress!!,
OnQueryResultListener<AccSensorConfiguration> { _, queryInfo ->
continuation.resume(queryInfo, null)
})
}
}
/**
* 设置Acc传感器参数
* @return Boolean
*/
suspend fun setAccConfig(accSensorConfiguration: AccSensorConfiguration): Boolean = withContext(Dispatchers.Default) {
if (connectMacAddress == null) {
return@withContext false
}
return@withContext suspendCancellableCoroutine<Boolean> { continuation ->
manager.setAccSensorConfig(connectMacAddress!!,accSensorConfiguration) {
continuation.resume(it, null)
}
}
}
/**
* 获取acc配置参数
*/
private fun getAccConfig(){
lifecycleScope.launch(Dispatchers.Main){
LoadingDialogUtil.showLoadingDialog()
val result = mConnectedViewModel.getAccConfig()
result?.let {
mAccConfig = it
when(it.sens){
1 -> binding.accAccTriggerSensitivitySelectorview.setSelectedIndex(3)
2 -> binding.accAccTriggerSensitivitySelectorview.setSelectedIndex(2)
3 -> binding.accAccTriggerSensitivitySelectorview.setSelectedIndex(1)
4 -> binding.accAccTriggerSensitivitySelectorview.setSelectedIndex(0)
else -> binding.accAccTriggerSensitivitySelectorview.setSelectedIndex(1)
}
when(it.window){
200 -> binding.accAccTestingDurationSelectorview.setSelectedIndex(0)
500 -> binding.accAccTestingDurationSelectorview.setSelectedIndex(1)
1000 -> binding.accAccTestingDurationSelectorview.setSelectedIndex(2)
2000 -> binding.accAccTestingDurationSelectorview.setSelectedIndex(3)
else -> binding.accAccTestingDurationSelectorview.setSelectedIndex(0)
}
}
LoadingDialogUtil.dismissLoadingDialog()
}
}
/**
* 获取acc配置参数
*/
private fun setAccConfig(accSensorConfiguration: AccSensorConfiguration){
lifecycleScope.launch(Dispatchers.Main){
LoadingDialogUtil.showLoadingDialog()
val result = mConnectedViewModel.setAccConfig(accSensorConfiguration)
LoadingDialogUtil.dismissLoadingDialog()
when(result){
true -> ToastUtils.showShort(getString(R.string.common_config_success))
else -> ToastUtils.showShort(getString(R.string.common_config_fail))
}
}
}
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
# 10. 查询和设置广播参数
BroadcastConfigEntity
| 名称 | 类型 | 描述 |
|---|---|---|
| param | List | 多个广播参数配置信息 |
BroadcastParamItem
| 名称 | 类型 | 描述 |
|---|---|---|
| en | boolean | 是否启用广播,true=开启,false=不开启 |
| m | String | 广播模式。m="norm"常规模式,m="trig"Acc触发模式 |
| f | String | 广播帧类型,f="dtlm" Combination frame,f="macr" Reoeater frame,f="ibeacon" ibeacon frame, |
| p | BroadcastParamDetail | 广播参数配置详情 |
BroadcastParamDetail
| 名称 | 类型 | 描述 |
|---|---|---|
| it | Integer | 广播间隔,范围100ms ~ 10000ms ,刻度为 100ms |
| tp | Integer | 广播功率,广播功率有8个:-40dBm、-20dBm、-16dBm、-12dBm、-8dBm、-4dBm、0dBm、4dBm、8dBm |
| phy | Integer | 广播速率。phy=0 1Mbps,phy=2 125Kbps |
/**
* 读取广播参数
* @param macAddress
* @param listener
*/
void getBroadcastConfig(String macAddress, OnQueryResultListener<BroadcastConfigEntity> listener);
/**
* 设置广播参数
* @param macAddress
* @param advConfigEntity
* @param listener
*/
void setBroadcastConfig(String macAddress, BroadcastConfigEntity broadcastConfigEntity, OnModifyConfigurationListener listener);
/**
* 获取广播参数
* @return AdvConfigEntity?
*/
suspend fun getAdvParams(): BroadcastConfigEntity? = withContext(Dispatchers.Default){
if(connectMacAddress == null){
return@withContext null
}
return@withContext suspendCancellableCoroutine<BroadcastConfigEntity?> { continuation ->
manager.getBroadcastConfig(connectMacAddress!!,
OnQueryResultListener<BroadcastConfigEntity> { _, queryInfo ->
continuation.resume(queryInfo, null)
})
}
}
/**
* 设置广播参数
* @return Boolean
*/
suspend fun setAdvParams(advConfigEntity: BroadcastConfigEntity): Boolean = withContext(Dispatchers.Default) {
if (connectMacAddress == null) {
return@withContext false
}
return@withContext suspendCancellableCoroutine<Boolean> { continuation ->
manager.setBroadcastConfig(connectMacAddress!!,advConfigEntity) {
continuation.resume(it, null)
}
}
}
/**
* 获取温湿度配置参数
*/
private fun getBroadcastConfig(){
lifecycleScope.launch(Dispatchers.Main){
LoadingDialogUtil.showLoadingDialog()
val result = mConnectViewModel.getAdvParams()
LoadingDialogUtil.dismissLoadingDialog()
result?.let {
mBroadcastParametersConfiguration = it
bindCombinateViewData(it)
bindRepeaterViewData(it)
}
}
}
private fun bindCombinateViewData(broadcastConfigEntity: BroadcastConfigEntity){
broadcastConfigEntity.let {
val combinationConfig = it.param?.firstOrNull { item -> item.f == BroadcastFrameType.BROADCAST_FRAME_COMBINATION.getCode() && item.m == currentMode }
//广播间隔
when(currentMode){
DeviceModel.TRIGGER_MODEL.getCode() -> {
binding.includeCombinationFrameConfig.combinationFrameToggleCheckbox.isChecked = combinationConfig?.isEn?:false
binding.includeCombinationFrameConfig.combinationFrameSettingContentLayout.visibility = when(binding.includeCombinationFrameConfig.combinationFrameToggleCheckbox.isChecked ){
true -> View.VISIBLE
false -> View.GONE
}
}
DeviceModel.NORMAL_MODEL.getCode() ->{
binding.includeCombinationFrameConfig.combinationFrameToggleCheckbox.visibility = View.GONE
binding.includeCombinationFrameConfig.combinationFrameSettingContentLayout.visibility = View.VISIBLE
}
else ->{}
}
binding.includeCombinationFrameConfig.advIntervalEdit.setText("${combinationConfig?.p?.it?:3000}")
binding.includeCombinationFrameConfig.advIntervalEdit.setSelection(binding.includeCombinationFrameConfig.advIntervalEdit.text.toString().length)
binding.includeCombinationFrameConfig.advIntervalSeekbar.progress = intervalRange.indexOf((combinationConfig?.p?.it?:3000)/intervalStep)
//广播功率
binding.includeCombinationFrameConfig.advTxPowerSeekbar.progress = powerRange.indexOf(combinationConfig?.p?.tp?:0)
binding.includeCombinationFrameConfig.advTxPowerTv.setText("${combinationConfig?.p?.tp?:0}")
//广播速率
binding.includeCombinationFrameConfig.advRateRadioGroup.check(when(combinationConfig?.p?.phy){
0 -> R.id.rate_1mbps_radioBtn
2 -> R.id.rate_125kbps_radioBtn
else -> R.id.rate_1mbps_radioBtn
})
}
}
private fun bindRepeaterViewData(broadcastConfigEntity: BroadcastConfigEntity){
broadcastConfigEntity.let {
val repeaterConfig = it.param?.firstOrNull { item -> item.f == BroadcastFrameType.BROADCAST_FRAME_REPEATER.getCode() && item.m == currentMode }
binding.includeRepeaterFrameConfig.repeaterFrameToggleCheckbox.isChecked = repeaterConfig?.isEn?:false
binding.includeRepeaterFrameConfig.repeaterFrameSettingContentLayout.visibility = when(binding.includeRepeaterFrameConfig.repeaterFrameToggleCheckbox.isChecked ){
true -> View.VISIBLE
false -> View.GONE
}
//广播间隔
binding.includeRepeaterFrameConfig.advIntervalEdit.setText("${repeaterConfig?.p?.it?:3000}")
binding.includeRepeaterFrameConfig.advIntervalEdit.setSelection(binding.includeRepeaterFrameConfig.advIntervalEdit.text.toString().length)
binding.includeRepeaterFrameConfig.advIntervalSeekbar.progress = intervalRange.indexOf((repeaterConfig?.p?.it?:3000)/intervalStep)
//广播功率
binding.includeRepeaterFrameConfig.advTxPowerSeekbar.progress = powerRange.indexOf(repeaterConfig?.p?.tp?:0)
binding.includeRepeaterFrameConfig.advTxPowerTv.setText("${repeaterConfig?.p?.tp?:0}")
}
}
/**
* 设置广播配置参数
*/
private inline fun setBroadcastConfig(broadcastConfigEntity: BroadcastConfigEntity){
lifecycleScope.launch(Dispatchers.Main){
LoadingDialogUtil.showLoadingDialog()
val result = mConnectViewModel.setAdvParams(broadcastConfigEntity)
LoadingDialogUtil.dismissLoadingDialog()
when(result){
true -> ToastUtils.showShort(getString(R.string.common_config_success))
else -> ToastUtils.showShort(getString(R.string.common_config_fail))
}
}
}
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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# 11.读取和配置IBeacon广播参数
AppleIBeaconParamConfiguration
| 名称 | 类型 | 描述 |
|---|---|---|
| data | List | 多个广播参数配置信息 |
AppleIBeaconParamItem
| 名称 | 类型 | 描述 |
|---|---|---|
| m | String | 广播模式。m="norm"常规模式,m="trig"Acc触发模式 |
| d | AppleIBeaconParamDetail | 广播参数配置详情 |
AppleIBeaconParamDetail
| 名称 | 类型 | 描述 |
|---|---|---|
| uuid | byte[] | uuid |
| major | int | major |
| minor | int | minor |
| cpwr | int | rssi |
/**
* 获取iBeacon广播参数
* @param macAddress
* @param listener
*/
void getAppleIBeaconParamsConfig(String macAddress, OnQueryResultListener<AppleIBeaconParamConfiguration> listener);
/**
* 设置iBeacon广播参数
* @param macAddress
* @param appleIBeaconParamConfiguration
* @param listener
*/
void setAppleIBeaconParamsConfig(String macAddress, AppleIBeaconParamConfiguration appleIBeaconParamConfiguration, OnModifyConfigurationListener listener);
/**
* 读取IBeacon广播参数
* @return Boolean
*/
suspend fun getIBeaconParamsConfig(): AppleIBeaconParamConfiguration? = withContext(Dispatchers.Default){
if(connectMacAddress == null){
return@withContext null
}
return@withContext suspendCancellableCoroutine<AppleIBeaconParamConfiguration?> { continuation ->
manager.getAppleIBeaconParamsConfig(connectMacAddress!!,
OnQueryResultListener<AppleIBeaconParamConfiguration> { _, queryInfo ->
continuation.resume(queryInfo, null)
})
}
}
/**
* 设置IBeacon广播参数
* @return Boolean
*/
suspend fun setIBeaconParamsConfig(advConfigEntity: AppleIBeaconParamConfiguration): Boolean = withContext(Dispatchers.Default) {
if (connectMacAddress == null) {
return@withContext false
}
return@withContext suspendCancellableCoroutine<Boolean> { continuation ->
manager.setAppleIBeaconParamsConfig(connectMacAddress!!,advConfigEntity) {
continuation.resume(it, null)
}
}
}
/**
* 获取IBeacon配置参数
*/
private fun getIBeaconConfig(){
lifecycleScope.launch(Dispatchers.Main){
LoadingDialogUtil.showLoadingDialog()
val result = mConnectViewModel.getIBeaconParamsConfig()
LoadingDialogUtil.dismissLoadingDialog()
result?.let {
bindIBeaconViewData(mBroadcastParametersConfiguration!!,it)
}
}
}
private fun bindIBeaconViewData(broadcastConfigEntity: BroadcastConfigEntity,appleIBeaconParamConfiguration: AppleIBeaconParamConfiguration){
broadcastConfigEntity.let {
val iBeaconConfig = it.param?.firstOrNull { item -> item.f == BroadcastFrameType.BROADCAST_FRAME_IBEACON.getCode() && item.m == currentMode }
val appleIBeaconContent = appleIBeaconParamConfiguration.data?.firstOrNull { item -> item.m == currentMode}
//开关
binding.includeIbeaconFrameConfig.ibeaconFrameToggleCheckbox.isChecked = iBeaconConfig?.isEn?:false
binding.includeIbeaconFrameConfig.ibeaconParamsSettingContentLayout.visibility = when(binding.includeIbeaconFrameConfig.ibeaconFrameToggleCheckbox.isChecked ){
true -> View.VISIBLE
false -> View.GONE
}
//广播间隔
binding.includeIbeaconFrameConfig.advIntervalEdit.setText("${iBeaconConfig?.p?.it?:3000}")
binding.includeIbeaconFrameConfig.advIntervalEdit.setSelection(binding.includeIbeaconFrameConfig.advIntervalEdit.text.toString().length)
binding.includeIbeaconFrameConfig.advIntervalSeekbar.progress = intervalRange.indexOf((iBeaconConfig?.p?.it?:3000)/intervalStep)
//广播功率
binding.includeIbeaconFrameConfig.advTxPowerSeekbar.progress = powerRange.indexOf(iBeaconConfig?.p?.tp?:0)
binding.includeIbeaconFrameConfig.advTxPowerTv.setText("${iBeaconConfig?.p?.tp?:0}")
//RSSI
binding.includeIbeaconFrameConfig.advRssiEdit.setText("${appleIBeaconContent?.d?.cpwr?:0}")
binding.includeIbeaconFrameConfig.advRssiEdit.setSelection(binding.includeIbeaconFrameConfig.advRssiEdit.text.toString().length)
binding.includeIbeaconFrameConfig.advRssiSeekbar.progress = rssiRange.indexOf(appleIBeaconContent?.d?.cpwr?:0)
//ibeacon uuid
if(appleIBeaconContent?.d?.uuid !=null){
binding.includeIbeaconFrameConfig.uuidEdit.setText(BytesOptUtil.bytesToUUID(appleIBeaconContent?.d?.uuid).toString().replace("-",""))
}
binding.includeIbeaconFrameConfig.uuidEdit.setSelection(binding.includeIbeaconFrameConfig.uuidEdit.text.toString().length)
//ibeacon major
binding.includeIbeaconFrameConfig.majorEdit.setText("${appleIBeaconContent?.d?.major?:""}")
binding.includeIbeaconFrameConfig.majorEdit.setSelection(binding.includeIbeaconFrameConfig.majorEdit.text.toString().length)
//ibeacon minor
binding.includeIbeaconFrameConfig.minorEdit.setText("${appleIBeaconContent?.d?.minor?:""}")
binding.includeIbeaconFrameConfig.minorEdit.setSelection(binding.includeIbeaconFrameConfig.minorEdit.text.toString().length)
//广播速率
binding.includeIbeaconFrameConfig.advRateRadioGroup.check(when(iBeaconConfig?.p?.phy){
0 -> R.id.rate_1mbps_radioBtn
2 -> R.id.rate_125kbps_radioBtn
else -> R.id.rate_1mbps_radioBtn
})
}
}
/**
* 设置IBeacon配置参数
*/
private fun setIBeaconConfig(appleIBeaconParamConfiguration: AppleIBeaconParamConfiguration){
lifecycleScope.launch(Dispatchers.Main){
LoadingDialogUtil.showLoadingDialog()
val result = mConnectViewModel.setIBeaconParamsConfig(appleIBeaconParamConfiguration)
LoadingDialogUtil.dismissLoadingDialog()
when(result){
true -> ToastUtils.showShort(getString(R.string.common_config_success))
else -> ToastUtils.showShort(getString(R.string.common_config_fail))
}
}
}
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
109
110
111
112
113
114
115
116
117
118
# 12. 读取和设置扫描过滤配置参数
ScanParamsConfig
| 名称 | 类型 | 描述 |
|---|---|---|
| param | List | 扫描参数 |
| filter | List<TypeMap<String, Object>> | 过滤参数 |
ScanParamItem
| 名称 | 类型 | 描述 |
|---|---|---|
| en | boolean | 是否启用,true=开启,false=不开启 |
| m | String | 模式。m="norm"常规模式,m="trig"Acc触发模式 |
| p | ScanParamDetail | 扫描参数配置详情 |
ScanParamDetail
| 名称 | 类型 | 描述 |
|---|---|---|
| it | float | 扫描间隔 |
| wd | float | 扫描窗口 |
| to | int | 扫描超时 |
| gap | int | 扫描重启间隔 |
| at | boolean | 扫描方式 true=主动扫描,false=被动扫描 |
/**
* 获取扫描参数
* @param macAddress
* @param listener
*/
void getScanConfig(String macAddress, OnQueryResultListener<ScanParamsConfig> listener);
/**
* 设置扫描参数
* @param macAddress
* @param scanParamsConfig
* @param listener
*/
void setScanConfig(String macAddress, ScanParamsConfig scanParamsConfig, OnModifyConfigurationListener listener);
/**
* 获取扫描过滤参数
*/
suspend fun getScanFilter(): ScanParamsConfig? = withContext(Dispatchers.Default){
if(connectMacAddress == null){
return@withContext null
}
return@withContext suspendCancellableCoroutine<ScanParamsConfig?> { continuation ->
manager.getScanConfig(connectMacAddress!!,
OnQueryResultListener<ScanParamsConfig> { _, queryInfo ->
try {
continuation.resume(queryInfo,null)
}catch (e: Exception){
LogUtil.e(e.message)
}
})
}
}
/**
* 设置扫描过滤参数
*/
suspend fun setScanFilter(scanFilter: ScanParamsConfig):Boolean = withContext(Dispatchers.Default){
if(connectMacAddress == null){
return@withContext false
}
return@withContext suspendCancellableCoroutine<Boolean> { continuation ->
manager.setScanConfig(connectMacAddress!!,scanFilter){
try {
continuation.resume(it, null)
}catch (e: Exception){
LogUtil.e(e.message)
}
}
}
}
/**
* 获取扫描过滤配置参数
*/
private fun getScanFilterConfig(){
lifecycleScope.launch(Dispatchers.Main){
LoadingDialogUtil.showLoadingDialog()
val result = mConnectViewModel.getScanFilter()
LoadingDialogUtil.dismissLoadingDialog()
result?.let {
val targetItem = it.param?.firstOrNull { paramItem -> paramItem.m == currentMode }
targetItem?.let { paramItem ->
binding.includeScanParamsConfig.scanTimeoutEdit.setText("${paramItem.p.to}")
binding.includeScanParamsConfig.scanRestartIntervalEdit.setText("${paramItem.p.gap}")
binding.includeScanParamsConfig.scanMethodRadioGroup.check(when(paramItem.p.isAt){
true -> R.id.active_scan_radioBtn
else -> R.id.passive_scan_radioBtn
})
binding.includeScanParamsConfig.scanParametersToggleCheckbox.isChecked = paramItem.isEn
binding.includeScanParamsConfig.scanParamsSettingContentLayout.visibility = when(binding.includeScanParamsConfig.scanParametersToggleCheckbox.isChecked){
true -> View.VISIBLE
else -> View.GONE
}
}
if(targetItem == null){
binding.includeScanParamsConfig.scanParametersToggleCheckbox.isChecked = false
binding.includeScanParamsConfig.scanParamsSettingContentLayout.visibility = when(binding.includeScanParamsConfig.scanParametersToggleCheckbox.isChecked){
true -> View.VISIBLE
else -> View.GONE
}
}
//过滤参数
it.filter?.forEachIndexed { index, map ->
val filterConditions:MutableList<FilterCondition> = mutableListOf()
for (one in FilterConditionTypeForRegular.entries){
map.forEach { (key, value) ->
if (one.typeName.equals(key, true)) {
if (value!=null){
if (value is ByteArray){
filterConditions.add(FilterCondition(one.typeName, one,BytesOptUtil.byteArrayToHex(value)))
}else{
filterConditions.add(FilterCondition(one.typeName, one,value))
}
}
return@forEach
}
}
}
if (filterConditions!=null&&filterConditions.isNotEmpty()){
mFilterConditionGroupList.add(FilterConditionGroup(filterConditions,initFilterConditionSelects()))
}
}
}
}
}
/**
* 获取扫描过滤配置参数
*/
private fun setScanFilterConfig(scanParamsConfig: ScanParamsConfig){
lifecycleScope.launch(Dispatchers.Main){
LoadingDialogUtil.showLoadingDialog()
val result = mConnectViewModel.setScanFilter(scanParamsConfig)
LoadingDialogUtil.dismissLoadingDialog()
when(result){
true -> ToastUtils.showShort(getString(R.string.common_config_success))
else -> ToastUtils.showShort(getString(R.string.common_config_fail))
}
}
}
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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# 13. 查询和配置Relay Control 数据上报
RelayControl
| 名称 | 类型 | 描述 |
|---|---|---|
| type | String | 数据上报方式,"mac"=mac上报,"imm"=major&minor上报方式 |
| aln | Integer | mac上报,mac的长度。长度范围{2,3,4,6} |
| max | Integer | 上报最大设备数量 范围:1~100 |
| cnt | Long |
/**
* 获取 Relay Control 配置
* @param macAddress MAC 地址
* @param listener 获取结果监听器
*/
void getRelayControl(String macAddress, OnQueryResultListener<RelayControl> listener);
/**
* 设置 Relay Control
* @param macAddress MAC 地址
* @param relayControl Relay Control参数
* @param listener 设置Relay Control结果监听器
*/
void setRelayControl(String macAddress, RelayControl relayControl, OnModifyConfigurationListener listener);
/**
* 获取Relay Contro参数
* @return RelayControl?
*/
suspend fun getRelayControl(): RelayControl? = withContext(Dispatchers.Default){
if(connectMacAddress == null){
return@withContext null
}
return@withContext suspendCancellableCoroutine<RelayControl?> { continuation ->
manager.getRelayControl(connectMacAddress!!,
OnQueryResultListener<RelayControl> { _, queryInfo ->
try {
continuation.resume(queryInfo,null)
}catch (e: Exception){
LogUtil.e(e.message)
}
})
}
}
/**
* 设置Relay Contro参数
* @return Boolean
*/
suspend fun setRelayControl(relayControl: RelayControl):Boolean = withContext(Dispatchers.Default){
if(connectMacAddress == null){
return@withContext false
}
return@withContext suspendCancellableCoroutine<Boolean> { continuation ->
manager.setRelayControl(connectMacAddress!!,relayControl){
try {
continuation.resume(it,null)
}catch (e: Exception){
LogUtil.e(e.message)
}
}
}
}
/**
* 获取RelayControl参数
*/
private fun getRelayControl() {
lifecycleScope.launch(Dispatchers.Main) {
LoadingDialogUtil.showLoadingDialog()
relayControl = mConnectedViewModel.getRelayControl()
LoadingDialogUtil.dismissLoadingDialog()
relayControl?.let {
binding.dataReportRadioGroup.check(when(it.type){
"mac" -> R.id.mac_radioBtn
"imm" -> R.id.major_minor_radioBtn
else -> R.id.mac_radioBtn
})
binding.reportMacByteCountLayout.visibility = when(it.type){
"mac" -> View.VISIBLE
"imm" -> View.GONE
else -> View.VISIBLE
}
if (it.aln!=null&&it.aln!=Int.MIN_VALUE){
binding.reportMacByteCountValueTv.text = it.aln.toString()
dataReportMacBytesCheckedIndex = dataArray.indexOf(it.aln.toString())
}
if (it.max!=null&&it.max!=Int.MIN_VALUE){
binding.reportDeviceCountValueEv.setText(it.max.toString())
}
}
}
}
/**
* 设置RelayControl参数
*/
private fun setRelayControl(relayControl: RelayControl) {
lifecycleScope.launch(Dispatchers.Main) {
LoadingDialogUtil.showLoadingDialog()
val result = mConnectedViewModel.setRelayControl(relayControl)
LoadingDialogUtil.dismissLoadingDialog()
when(result){
true -> ToastUtils.showShort(getString(R.string.common_config_success))
else -> ToastUtils.showShort(getString(R.string.common_config_fail))
}
}
}
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
#
# 14. 固件升级。
/**
* 固件升级
* @param context 上下文
* @param macAddress mac地址
* @param isLinkUpgrade 是否连接升级还是OTA升级 true:url升级 ,false:OTA升级
* @param filePath OTA文件路径,url升级时可为null
* @param upgradeData OTA数据,url升级时可为null
* @param target 升级固件目标,OTA数据,默认 target="main",url升级时为target="app"
* @param listener
*/
void firmwareUpgrade(@NonNull Context context,@NonNull String macAddress, boolean isLinkUpgrade, String filePath, @NonNull byte[] upgradeData, String target, OnFirmwareUpgradeListener listener) ;
/**
* 调制到系统文件
*/
private fun gotoSystemFilePage() {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
intent.type = "*/*"
launcherActivityResult.launch(intent)
}
//读取固件升级回调
private val launcherActivityResult =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) {
val fileUri = it.data?.data
LogUtil.e("fileUri","fileUri=${fileUri.toString()}")
fileUri?.let { uri ->
handleFile(uri)
}
}
}
private fun handleFile(fileUri: Uri) {
//获取压缩包文件名
var fileName = ""
//获取压缩包路径 uri转path
val cursor =this.contentResolver.query(fileUri, null, null, null, null, null)
try {
if (cursor != null && cursor.moveToFirst()) {
fileName = cursor.getString(
cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
)
} else {
}
} finally {
cursor?.close()
}
val zipFilePath = FileUtil.getFileAbsolutePath(this, fileUri)
toFirmwareUpgradePage(zipFilePath)
}
/**
* 解析升级包
* @param filePath String
*/
private fun dealFileData(filePath: String) {
MinewExecutors.getInstance().getDiskIO().execute {
try {
val bininputstream = ZipUtil.readZipFile(filePath, ".bin")
val bufferedInputStream = BufferedInputStream(bininputstream)
val allData = ArrayList<Byte>()
//每次读取4k
val buffer = ByteArray(1024 * 4)
var bytesRead = 0
var tempLength = 0
while (bufferedInputStream.read(buffer).also { bytesRead = it } != -1) {
for (i in 0 until bytesRead) {
val by = buffer[i]
allData.add(by)
}
tempLength += bytesRead
}
val fileData = ByteArray(allData.size)
for (i in allData.indices) {
fileData[i] = allData[i]
}
bufferedInputStream.close()
//调用升级指令
firmwareUpgrade(this@FirmwareUpgradeActivity,filePath,fileData, "main")
} catch (e: FileNotFoundException) {
e.printStackTrace()
chooseFileError(R.string.common_file_select_error)
} catch (e: IOException) {
e.printStackTrace()
chooseFileError(R.string.common_file_select_error)
}
}
}
/**
* 固件升级
*/
private fun firmwareUpgrade(
context: Context,
filePath:String,
fileByte: ByteArray,
target: String
) {
mConnectedViewModel.firmwareUpgrade(context,filePath, fileByte,target,
progressCallBack = { progress ->
WaitDialog.show(getString(R.string.common_upgrading), (progress / 100f))
binding.progressBarSimpleCustom.progress = progress.toFloat()
},
successCallBack = {
WaitDialog.dismiss()
ToastUtils.showShort(R.string.common_firmware_upgrade_successfully)
},
failCallBack = {
WaitDialog.dismiss()
ToastUtils.showShort(R.string.common_firmware_upgrade_failure2)
}
)
}
/**
* 固件升级
*/
fun firmwareUpgrade(
context: Context,
filePath:String,
fileByte: ByteArray,
target:String,
progressCallBack:(progress:Int) -> Unit,
successCallBack:() -> Unit,
failCallBack:() -> Unit,
){
manager.firmwareUpgrade(context,connectMacAddress!!,false,filePath,fileByte,target,
object : OnFirmwareUpgradeListener {
override fun updateProgress(progress: Int) {
//升级包数据写入进度
progressCallBack(progress)
}
override fun upgradeSuccess() {
// 升级成功回调,此时设备会主动跟手机断开连接,所以会触发OnConnStateListener回调
successCallBack()
}
override fun upgradeFailed() {
//升级失败
failCallBack()
}
})
}
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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# 15. 查询历史记录
历史数据可以查询门磁、ACC、温湿度、扫描数据等类型数据。
/**
* 获取设备存储数据
* @param queryAllData true=查询全部,false=按时间段查询。注意:建议按时间段查询,查询全部可能受限制手机运行内存导致查询失败。
* @param startTime Long 开始查询时间。单位秒
* @param endTime Long 结束查询时间。单位秒
* @param systemTime Long 手机系统当前时间,单位秒。建议直接传:System.currentTimeMillis()/1000
*/
private fun getDeviceStorageData(queryAllData:Boolean, sensorType: StorageDataBlockType, startTime:Long, endTime:Long, systemTime: Long){
lifecycleScope.launch(Dispatchers.Main) {
LoadingDialogUtil.showLoadingDialog()
lifecycleScope.launch(Dispatchers.IO) {
val storageSearch = when(sensorType){
StorageDataBlockType.Temperature_V2_x100) -> StorageIndexRecordSearcher<StorageActualDataHumidityAndTemperatureX100>()
StorageDataBlockType.Contact_Sensor_State -> StorageIndexRecordSearcher<StorageActualDataContactSensorState>()
StorageDataBlockType.Motion -> StorageIndexRecordSearcher<StorageActualDataMotion>()
StorageDataBlockType.Advertising_Report -> StorageIndexRecordSearcher<StorageActualDataScanResultAdvertisingReportData>()
else -> StorageIndexRecordSearcher<StorageActualDataHumidityAndTemperatureX100>()
}
storageSearch.getStorageIndexRecordsNeedSearchFile(
this@HistoryDataTimePickerActivity,
mConnectedViewModel.connectMacAddress,
false,
StorageIndexRecordCondition()
.setStorageDataBlockType(sensorType)
.setInstance(StorageIndexInstance.INSTANCE_DEFAULT)
.setStartTime(when (queryAllData){
true ->0
else ->startTime
})
.setEndTime(when (queryAllData){
true ->systemTime
else ->endTime
})
) { resultList,exception ->
lifecycleScope.launch (Dispatchers.Main){
LoadingDialogUtil.dismissLoadingDialog()
if (exception==null
&&resultList!=null
&&!resultList.isEmpty()
){
when(sensorType){
StorageDataBlockType.Temperature_V2_x100 -> {
}
StorageDataBlockType.Contact_Sensor_State-> {
}
StorageDataBlockType.Motion-> {
}
StorageDataBlockType.Advertising_Report-> {
}
else -> {}
}
}else{
}
}
}
}
}
}
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
# 16.清除历史数据
/**
* 清除历史数据
* @param macAddress
* @param force 清除数据 force=true
* @param listener
*/
void clearHistory(@NonNull String macAddress, boolean force, OnModifyConfigurationListener listener);
/**
* 设置清除历史数据
* @return force
*/
suspend fun clearHistory(force:Boolean):Boolean = withContext(Dispatchers.Default){
if(connectMacAddress == null){
return@withContext false
}
return@withContext suspendCancellableCoroutine<Boolean> { continuation ->
manager.clearHistory(connectMacAddress!!,force) {
continuation.resume(it, null)
}
}
}
/**
* 清除设备历史数据
*/
private fun cleanHistoryData(){
lifecycleScope.launch(Dispatchers.Main){
LoadingDialogUtil.showLoadingDialog()
val result = mConnectedViewModel.clearHistory( true)
LoadingDialogUtil.dismissLoadingDialog()
ToastUtils.showShort(when(result){
true -> R.string.common_clear_successfully
else -> R.string.common_clear_failed
})
}
}
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
# 17.获取设备支持的命令列表
CommandListEntity
| 名称 | 类型 | 描述 |
|---|---|---|
| groupId | int | 对应nanolink协议的groupId |
| commandIds | List | 对应nanolink协议的commandId |
/**
* 获取设备支持的命令列表
* @param macAddress mac地址
* @param listener 监听器
*/
void getDeviceSupportCommandList(@NonNull String macAddress, OnQueryResultListener<List<CommandListEntity>> listener);
/**
* 查询设备支持的命令列表
* @return CommandListEntity?
*/
suspend fun getDeviceSupportCommandList(): List<CommandListEntity>? = withContext(Dispatchers.Default){
if(connectMacAddress == null){
return@withContext null
}
return@withContext suspendCancellableCoroutine<List<CommandListEntity>?> { continuation ->
manager.getDeviceSupportCommandList(connectMacAddress!!,
OnQueryResultListener<List<CommandListEntity>> { _, queryInfo ->
continuation.resume(queryInfo, null)
})
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 文档更新记录
- 2026/09/20 新增 MTB13设备操作基本功能 API