# MinewMTB13Kit Documentation
This SDK only supports Bluetooth devices manufactured by Minew.
The SDK helps developers handle all interactions between the phone and Bluetooth devices, including: scanning for devices, advertising data, connecting to devices, writing data to devices, and receiving data from devices, etc.
Currently, the SDK only supports the MTB13 industrial high-protection asset tag device.
# Preparations
Overall architecture: MTB13BleDevicesManager is the device management class, which remains a singleton for the lifetime of the APP. MTB13Entity is the device entity class; the SDK generates one instance for each device, used both after scanning and after connection. It internally contains the device's advertising data, which is updated continuously as the device broadcasts during scanning.
MTB13BleDevicesManager : the device management class, which can scan for nearby devices, connect to them, verify them, and more.
MTB13Entity : the device instance obtained during scanning, which inherits from BaseBleDeviceEntity
# I. Import into the Project
# 1. Development Environment
The SDK requires a minimum of Android 7.0, corresponding to API Level 24. Set minSdkVersion to 24 or higher in the module's build.gradle:
android {
defaultConfig {
applicationId "com.xxx.xxx"
minSdkVersion 24
}
}
2
3
4
5
6
# 2. Add the Library
Add the AAR package to the module's libs folder, and add the following statements to the module's build.gradle (add the dependency directly):
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
To add the .so library files, add the following configuration to the App directory's 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. Add BLE-related Permissions
The following permissions are required in AndroidManifest.xml. If targetSdkVersion is greater than 23, you need to implement permission management to obtain these permissions:
<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
# II. Usage
The SDK is divided into three phases: scanning, connection, and read/write.
# 1. Start Scanning
On Android 6.0 and above, before performing a BLE scan, you must first obtain Bluetooth permissions and enable the location service.
To start Bluetooth scanning, Bluetooth must be enabled first. If you initiate a scan without enabling Bluetooth, the APP will crash. You can use BLETool.checkBluetooth(this) to check whether Bluetooth is enabled. If it is not enabled, enable Bluetooth first.
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 -> {
}
}
}
/**
* Start scanning
*/
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>) {
}
})
}
/**
* Stop scanning
*/
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
84
85
Note: During scanning, do not frequently call the manager.startScan() and manager.stopScan() methods. Set a reasonable scan duration. Avoid calling manager.startScan() and manager.stopScan() multiple times within one minute, otherwise the Bluetooth stack may malfunction. If problems occur, go to the phone's system settings → Bluetooth → manually toggle Bluetooth off and then on.
During scanning, the APP can obtain some of the device's current data through the SDK. As shown below, the device data is obtained via MTB13Entity and stored in the advertising frame object.
The SDK provides BaseBleDeviceEntity as the base class of MTB13Entity for storing the device's common data, as shown in the table below:
| Name | Type | Description |
|---|---|---|
| macAddress | String | Device MAC address |
| name | String | Device name |
| rssi | int | RSSI (signal strength) |
BaseBleDeviceEntity also holds a BaseNanoLinkFrame, which is used internally to store the device's advertising data frames captured during scanning. You can retrieve it as follows:
val mtb13AdvFrame : Mtb13AdvFrame? = item.nanoLinkFrame?.let {it as Mtb13AdvFrame}
// MAC address
val mac = mtb13AdvFrame?.macAddress
// deviceName
String deviceName = mtb13AdvFrame?.deviceName
// battery percentage; default: Integer.MIN_VALUE indicates battery level not yet obtained
val battery = mtb13AdvFrame?.battery
// firmware version
val firmwareVersion = mtb13AdvFrame?.firmwareVersion
// temperature data
mtb13AdvFrame?.temperatureHumidityList?.let { htList ->
val temperature = htList[0].temperature
}
// motion state
mtb13AdvFrame?.motionState?.let { motion ->
val state = motion.motionState
}
// door/window (contact sensor) state
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
The MTB13 industrial high-protection asset tag device has 1 advertising frame type.
Device frame
- Mtb13AdvFrame
| Name | Type | Description |
|---|---|---|
| mac | String | Firmware MAC address |
| deviceName | String | Device name |
| firmwareVersion | String | Version number |
| battery | int | Battery percentage; default: Integer.MIN_VALUE indicates battery level not obtained |
| motionState | MotionState | ACC motion state; motionState=0 stationary, motionState=1 moving |
| contactSensorState | ContactSensorState | Door/window state; contactSensorState=0 closed, contactSensorState=1 open |
| temperatureHumidityList | List | Temperature data; temperature=-128f is invalid. Any other value is valid |
# 2. Connection
Before connecting, you generally need to stop scanning first. The SDK provides methods for connecting and disconnecting.
val mBleManager = MTB13BleDevicesManager.getInstance()
// Stop scanning
mBleManager.stopScan(context);
// Connect: module is the device to be connected
val module:MTB13Entity;
mBleManager.connect(context,module)
// Disconnect: macAddress is the device MAC
mBleManager.disConnect(macAddress)
2
3
4
5
6
7
8
Note: Before connecting to a device, make sure the device has been scanned. If the device's advertisement has not been scanned, calling the connect method will fail.
After calling connect(), the SDK monitors the connection process via state callbacks.
// Set the listener
mBleManager.setOnConnStateListener {macAddress, connectionState ->
when (it) {
BleConnectionState.Connecting -> {
// This state is reported after calling connect()
}
BleConnectionState.Connected -> {
// Initial connection succeeded as a transitional phase; the connection is not truly established yet
}
BleConnectionState.EnterAuthenticatePassword -> {
}
BleConnectionState.AuthenticateSuccess ->{
}
BleConnectionState.AuthenticateFail ->{
}
BleConnectionState.ConnectComplete -> {
// Device connection is complete; only then can you operate the device or navigate to another screen via the methods provided by MTB13BleDevicesManager
}
BleConnectionState.Bond_None -> {
// Bonding callback: this is the invalid bonding state
}
BleConnectionState.Bond_Bonding -> {
// Bonding callback: this is the bonding-in-progress state
}
BleConnectionState.Bond_Bonded -> {
// Bonding callback: this is the bonded (completed) state
// Only after bonding is complete can you operate the device via the methods provided by MTB13BleDevicesManager
}
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
During the connection process, the SDK returns multiple connection states to the APP, and the APP must handle them properly.
- BleConnectionState.Connecting, BleConnectionState.Connected: The device is connecting. Do not perform time-consuming operations in this state, because the SDK is discovering services and sending authentication data, etc.
- BleConnectionState.Bond_None: Invalid bonding state.
- BleConnectionState.Bond_Bonding: Device is bonding.
- BleConnectionState.Bond_Bonded: Bonding completed; only then can you operate the device via the methods provided by MTB13BleDevicesManager.
- BleConnectionState.ConnectComplete: The device is fully connected; you can now perform read/write operations, such as configuring advertising parameters, reading historical data, etc.
- BleConnectionState.Disconnect: Callback triggered when connection fails or the device disconnects.
# 3. Device Configuration Read/Write Operations
The APIs for reading and writing device configuration are as follows, invoked via the MTB13BleDevicesManager.getInstance() object:
# 1. Synchronize Device Time with Phone's Current Time
By default, the SDK will automatically grant the device the phone's time during the connection process.
// First, check whether Bluetooth is enabled. The phone's CurrentTimeService can only be started when Bluetooth is on. If Bluetooth is off and you try to enable it, some Android systems may throw errors. You must also monitor the Bluetooth state: start the service via startSyncTimeServer when Bluetooth turns on, and stop it via closeSyncTimeServer when Bluetooth turns off. Note: when the Activity's onDestroy is called, you must also stop the service via closeSyncTimeServer.
val mBleManager = MTB13BleDevicesManager.getInstance()
// Start the service
mBleManager.startSyncTimeServer(context)
// Stop the service
mBleManager.closeSyncTimeServer(context)
// Some phones may occasionally fail at time synchronization, so you need to check whether time synchronization succeeded. It is recommended to check 3 seconds after the device is connected.
mBleManager.isDeviceReadSyncTime(context);
// If the time synchronization did not succeed, you need to proactively send a time-sync command to the device.
mBleManager.writeTimeToDevice(macAddress, onWriteTimeToDeviceListener);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 2. Get Firmware Version
FirmwareVersionModel
| Name | Type | Description |
|---|---|---|
| versionInfoList | List | Collection of version information |
VersionInfo
| Name | Type | Description |
|---|---|---|
| firmwareName | String | Firmware name |
| firmwareType | int | Default value -1 |
| firmwareVersion | String | Firmware version number |
| slot | String | Slot |
| methods | ArrayList |
/**
* Get firmware version
* @param macAddress Device MAC address
* @param listener Callback listener
*/
void getFirmwareVersion(@NonNull String macAddress, OnQueryResultListener<FirmwareVersionModel> listener);
/**
* Get firmware version info
* @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)
})
}
}
/**
* Get version information
*/
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. Factory Reset
/**
* String macAddress
* Factory reset
*
* @param macAddress Device MAC address
* @param listener Callback listener
*/
void reset(@NonNull String macAddress, OnModifyConfigurationListener listener);
/**
* Factory reset
* @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)
}
}
}
/**
* Factory reset
*/
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. Power Off
/**
* Power off
*
* @param macAddress Device MAC address
* @param listener Callback listener
*/
void powerOff(@NonNull String macAddress, OnModifyConfigurationListener listener);
/**
* Power off
* @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)
}
}
}
/**
* Power off
*/
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. Reboot
/**
* Reboot
* @param macAddress Device MAC address
*/
void reboot( String macAddress, OnModifyConfigurationListener listener);
/**
* Reboot
* @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)
}
}
}
/**
* Reboot device
*/
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. Set Static Passkey
/**
* Set static passkey
* @param macAddress Device MAC address
* @param passkey Password, 6-digit, range 000000 - 999999
* @param listener Callback listener
*/
void setStaticPassKey(@NonNull String macAddress, int passkey, OnModifyConfigurationListener listener);
/**
* Change device password
* @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)
}
}
}
/**
* Set device password
* @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. Query and Set Device Name
/**
* Get device name
* @param macAddress Device MAC address
* @param listener Callback listener
*/
void getDeviceName( String macAddress, OnQueryResultListener<String> listener);
/**
* Set device name
* @param macAddress Device MAC address
* @param name Device name, max length 9 characters
* @param listener Callback listener
*/
void setDeviceName( String macAddress, String name, OnModifyConfigurationListener listener);
/**
* Get device name
* @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)
})
}
}
/**
* Set device name
* @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)
}
}
}
/**
* Get device name
*/
private fun getDeviceName(){
lifecycleScope.launch(Dispatchers.Main){
LoadingDialogUtil.showLoadingDialog()
val result = mConnectedViewModel.getDeviceName()
LoadingDialogUtil.dismissLoadingDialog()
result?.let {
binding.tvDeviceName.text = it
}
}
}
/**
* Set device name; name length limited to within 9 characters
*/
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. Query HT Sensor Parameters
TemperatureHumidityConfiguration
| Name | Type | Description |
|---|---|---|
| intvl | int | T&H sampling interval, in seconds |
/**
* Query HT sensor parameters
* @param macAddress
* @param listener
*/
void getTemperatureHumidityConfig(String macAddress, OnQueryResultListener<TemperatureHumidityConfiguration> listener);
/**
* Set HT sensor parameters
* @param macAddress
* @param htSensorConfig
* @param listener
*/
void setTemperatureHumidityConfig(String macAddress, TemperatureHumidityConfiguration temperatureHumidityConfiguration, OnModifyConfigurationListener listener);
/**
* Get T&H parameters
* @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)
})
}
}
/**
* Set T&H parameters
* @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)
}
}
}
/**
* Get T&H configuration parameters
*/
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"
}
}
}
/**
* Set T&H configuration parameters
*/
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. Query and Set ACC
AccSensorConfiguration
| Name | Type | Description |
|---|---|---|
| sens | int | Trigger sensitivity; levels: 1,2,3,4. Higher value means more sensitive |
| window | int | Detection duration; levels: 200,500,1000,2000. |
/**
* Query ACC sensor parameters
* @param macAddress
* @param listener
*/
void getAccSensorConfig(String macAddress, OnQueryResultListener<AccSensorConfiguration> listener);
/**
* Set ACC sensor parameters
* @param macAddress
* @param listener
*/
void setAccSensorConfig(String macAddress, AccSensorConfiguration accSensorConfiguration, OnModifyConfigurationListener listener);
/**
* Get ACC sensor parameters
* @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)
})
}
}
/**
* Set ACC sensor parameters
* @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)
}
}
}
/**
* Get ACC configuration parameters
*/
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()
}
}
/**
* Get ACC configuration parameters
*/
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. Query and Set Advertising Parameters
BroadcastConfigEntity
| Name | Type | Description |
|---|---|---|
| param | List | Multiple advertising parameter configurations |
BroadcastParamItem
| Name | Type | Description |
|---|---|---|
| en | boolean | Whether advertising is enabled; true=on, false=off |
| m | String | Advertising mode. m="norm" normal mode, m="trig" ACC trigger mode |
| f | String | Advertising frame type; f="dtlm" Combination frame, f="macr" Repeater frame, f="ibeacon" iBeacon frame |
| p | BroadcastParamDetail | Advertising parameter configuration details |
BroadcastParamDetail
| Name | Type | Description |
|---|---|---|
| it | Integer | Advertising interval, range 100ms ~ 10000ms, step 100ms |
| tp | Integer | Advertising TX power; 8 levels available: -40dBm, -20dBm, -16dBm, -12dBm, -8dBm, -4dBm, 0dBm, 4dBm, 8dBm |
| phy | Integer | Advertising PHY rate. phy=0 1Mbps, phy=2 125Kbps |
/**
* Read advertising parameters
* @param macAddress
* @param listener
*/
void getBroadcastConfig(String macAddress, OnQueryResultListener<BroadcastConfigEntity> listener);
/**
* Set advertising parameters
* @param macAddress
* @param advConfigEntity
* @param listener
*/
void setBroadcastConfig(String macAddress, BroadcastConfigEntity broadcastConfigEntity, OnModifyConfigurationListener listener);
/**
* Get advertising parameters
* @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)
})
}
}
/**
* Set advertising parameters
* @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)
}
}
}
/**
* Get T&H configuration parameters
*/
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 }
// Advertising interval
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)
// Advertising TX power
binding.includeCombinationFrameConfig.advTxPowerSeekbar.progress = powerRange.indexOf(combinationConfig?.p?.tp?:0)
binding.includeCombinationFrameConfig.advTxPowerTv.setText("${combinationConfig?.p?.tp?:0}")
// Advertising PHY rate
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
}
// Advertising interval
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)
// Advertising TX power
binding.includeRepeaterFrameConfig.advTxPowerSeekbar.progress = powerRange.indexOf(repeaterConfig?.p?.tp?:0)
binding.includeRepeaterFrameConfig.advTxPowerTv.setText("${repeaterConfig?.p?.tp?:0}")
}
}
/**
* Set advertising configuration parameters
*/
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. Read and Configure iBeacon Advertising Parameters
AppleIBeaconParamConfiguration
| Name | Type | Description |
|---|---|---|
| data | List | Multiple advertising parameter configurations |
AppleIBeaconParamItem
| Name | Type | Description |
|---|---|---|
| m | String | Advertising mode. m="norm" normal mode, m="trig" ACC trigger mode |
| d | AppleIBeaconParamDetail | Advertising parameter configuration details |
AppleIBeaconParamDetail
| Name | Type | Description |
|---|---|---|
| uuid | byte[] | UUID |
| major | int | Major |
| minor | int | Minor |
| cpwr | int | RSSI (measured power) |
/**
* Get iBeacon advertising parameters
* @param macAddress
* @param listener
*/
void getAppleIBeaconParamsConfig(String macAddress, OnQueryResultListener<AppleIBeaconParamConfiguration> listener);
/**
* Set iBeacon advertising parameters
* @param macAddress
* @param appleIBeaconParamConfiguration
* @param listener
*/
void setAppleIBeaconParamsConfig(String macAddress, AppleIBeaconParamConfiguration appleIBeaconParamConfiguration, OnModifyConfigurationListener listener);
/**
* Read iBeacon advertising parameters
* @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)
})
}
}
/**
* Set iBeacon advertising parameters
* @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)
}
}
}
/**
* Get iBeacon configuration parameters
*/
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}
// Switch (enable/disable)
binding.includeIbeaconFrameConfig.ibeaconFrameToggleCheckbox.isChecked = iBeaconConfig?.isEn?:false
binding.includeIbeaconFrameConfig.ibeaconParamsSettingContentLayout.visibility = when(binding.includeIbeaconFrameConfig.ibeaconFrameToggleCheckbox.isChecked ){
true -> View.VISIBLE
false -> View.GONE
}
// Advertising interval
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)
// Advertising TX power
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)
// Advertising PHY rate
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
})
}
}
/**
* Set iBeacon configuration parameters
*/
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. Read and Set Scan Filter Configuration
ScanParamsConfig
| Name | Type | Description |
|---|---|---|
| param | List | Scan parameters |
| filter | List<TypeMap<String, Object>> | Filter parameters |
ScanParamItem
| Name | Type | Description |
|---|---|---|
| en | boolean | Whether enabled; true=on, false=off |
| m | String | Mode. m="norm" normal mode, m="trig" ACC trigger mode |
| p | ScanParamDetail | Scan parameter configuration details |
ScanParamDetail
| Name | Type | Description |
|---|---|---|
| it | float | Scan interval |
| wd | float | Scan window |
| to | int | Scan timeout |
| gap | int | Scan restart interval |
| at | boolean | Scan type; true=active scanning, false=passive scanning |
/**
* Get scan parameters
* @param macAddress
* @param listener
*/
void getScanConfig(String macAddress, OnQueryResultListener<ScanParamsConfig> listener);
/**
* Set scan parameters
* @param macAddress
* @param scanParamsConfig
* @param listener
*/
void setScanConfig(String macAddress, ScanParamsConfig scanParamsConfig, OnModifyConfigurationListener listener);
/**
* Get scan filter parameters
*/
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)
}
})
}
}
/**
* Set scan filter parameters
*/
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)
}
}
}
}
/**
* Get scan filter configuration parameters
*/
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
}
}
// Filter parameters
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()))
}
}
}
}
}
/**
* Get scan filter configuration parameters
*/
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. Query and Configure Relay Control Data Reporting
RelayControl
| Name | Type | Description |
|---|---|---|
| type | String | Data reporting method; "mac"=MAC reporting, "imm"=major&minor reporting |
| aln | Integer | For MAC reporting, the length of the MAC. Range {2,3,4,6} |
| max | Integer | Maximum number of reported devices; range 1~100 |
| cnt | Long |
/**
* Get Relay Control configuration
* @param macAddress MAC address
* @param listener Callback for the query result
*/
void getRelayControl(String macAddress, OnQueryResultListener<RelayControl> listener);
/**
* Set Relay Control
* @param macAddress MAC address
* @param relayControl Relay Control parameters
* @param listener Callback for the set result
*/
void setRelayControl(String macAddress, RelayControl relayControl, OnModifyConfigurationListener listener);
/**
* Get Relay Control parameters
* @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)
}
})
}
}
/**
* Set Relay Control parameters
* @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)
}
}
}
}
/**
* Get RelayControl parameters
*/
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())
}
}
}
}
/**
* Set RelayControl parameters
*/
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. Firmware Upgrade
/**
* Firmware upgrade
* @param context Context
* @param macAddress Device MAC address
* @param isLinkUpgrade Whether to upgrade via connection or OTA; true: URL upgrade, false: OTA upgrade
* @param filePath OTA file path; can be null when upgrading via URL
* @param upgradeData OTA data; can be null when upgrading via URL
* @param target Upgrade target firmware; OTA data, default target="main", target="app" when upgrading via URL
* @param listener
*/
void firmwareUpgrade(@NonNull Context context,@NonNull String macAddress, boolean isLinkUpgrade, String filePath, @NonNull byte[] upgradeData, String target, OnFirmwareUpgradeListener listener) ;
/**
* Navigate to the system file picker
*/
private fun gotoSystemFilePage() {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
intent.type = "*/*"
launcherActivityResult.launch(intent)
}
// Firmware upgrade file-selection callback
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) {
// Get the compressed package file name
var fileName = ""
// Get the compressed package path (convert URI to 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)
}
/**
* Parse the upgrade package
* @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>()
// Read 4 KB each time
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()
// Invoke the upgrade command
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)
}
}
}
/**
* Firmware upgrade
*/
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)
}
)
}
/**
* Firmware upgrade
*/
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) {
// Upgrade package data write progress
progressCallBack(progress)
}
override fun upgradeSuccess() {
// Upgrade success callback; at this point the device will actively disconnect from the phone, which triggers the OnConnStateListener callback
successCallBack()
}
override fun upgradeFailed() {
// Upgrade failed
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. Query Historical Records
Historical data can be queried by type, including contact sensor, ACC, temperature & humidity, and scan data, etc.
/**
* Get device stored data
* @param queryAllData true=query all, false=query by time range. Note: querying by time range is recommended; querying all may fail due to the phone's limited RAM.
* @param startTime Long Start query time, in seconds
* @param endTime Long End query time, in seconds
* @param systemTime Long Phone's current system time, in seconds. It is recommended to pass: 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. Clear Historical Data
/**
* Clear historical data
* @param macAddress
* @param force Whether to force-clear data; force=true
* @param listener
*/
void clearHistory(@NonNull String macAddress, boolean force, OnModifyConfigurationListener listener);
/**
* Clear historical data
* @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)
}
}
}
/**
* Clear device historical data
*/
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. Get the List of Commands Supported by the Device
CommandListEntity
| Name | Type | Description |
|---|---|---|
| groupId | int | Corresponds to the groupId of the nanolink protocol |
| commandIds | List | Corresponds to the commandId(s) of the nanolink protocol |
/**
* Get the list of commands supported by the device
* @param macAddress Device MAC address
* @param listener Callback listener
*/
void getDeviceSupportCommandList(@NonNull String macAddress, OnQueryResultListener<List<CommandListEntity>> listener);
/**
* Query the list of commands supported by the device
* @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
# Document Update Log
- 2026/09/20 Added basic MTB13 device operation APIs