# MinewMSG01Kit Documentation
This SDK only supports Bluetooth devices manufactured by Minew.
The SDK helps developers handle all tasks between the phone and Bluetooth devices, including: scanning devices, advertising data, connecting devices, writing data to devices, receiving data from devices, and more.
Currently, the SDK only supports the MSG01 all-in-one air quality sensor device.
# Prerequisites
Overall framework: Msg01BleDevicesManager is the device manager class and remains a singleton while the app is running. MSG01Entity is the device instance class; the kit creates an instance for each device, used both after scanning and after connecting. It contains the device's advertising data, which is continuously updated as the device keeps advertising during scanning.
Msg01BleDevicesManager : Device manager class. It can scan surrounding devices, connect to them, verify them, and so on.
MSG01Entity : The smart positioning badge device instance obtained during scanning; it inherits from BaseBleDeviceEntity
# I. Import into the Project
# 1. Development Environment
The SDK requires Android 7.0 minimum, 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 Libraries
Add the aar package to the module's libs folder, and add the following statements in the module's build.gradle (add the dependencies directly):
implementation files('libs\\SDK_MSG01-release.aar')
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
Add the .so library files. Add the following configuration in the build.gradle under the app directory:
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 must perform permission management to obtain the 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_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<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
11
12
13
# II. Usage
The SDK is divided into three phases: scanning, connection, and read/write.
# 1. Start Scanning
On Android 6.0 and above, performing a BLE scan requires obtaining Bluetooth permissions and turning on the location switch before scanning can proceed.
Starting a Bluetooth scan requires Bluetooth to be turned on first; if you start scanning without enabling Bluetooth, the app will crash. 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@BusiMsg01ActivityScanDevicesActivity)) {
BluetoothState.BLE_NOT_SUPPORT -> {
Toast.makeText(this@BusiMsg01ActivityScanDevicesActivity, "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 = Msg01BleDevicesManager.getInstance()
manager.startScan(ModuleMSG01Application.getApplication().mApplication, 5*60*1000,object :
OnScanDevicesResultListener<MSG01Entity> {
override fun onScanResult(scanList: MutableList<MSG01Entity>) {
}
override fun onStopScan(scanList: MutableList<MSG01Entity>) {
}
})
}
/**
* Stop scanning
*/
private fun stopScan() {
val manager = Msg01BleDevicesManager.getInstance()
manager.stopScan(ModuleMSG01Application.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
Note: During scanning, do not call manager.startScan() and manager.stopScan() frequently. Set a reasonable scan duration. Avoid calling manager.startScan() and manager.stopScan() multiple times within one minute, as this may cause issues with the Bluetooth stack. If problems occur, go to the phone's system settings -> Bluetooth toggle -> manually turn it off and back on.
During scanning, the app can obtain part of the device's current data through the SDK. As shown below, retrieve the device data via MSG01Entity; this data is stored in the advertising frame object.
The SDK provides BaseBleDeviceEntity as the base class of MSG01Entity to store the device's common data, as shown in the table below:
| Name | Type | Description |
|---|---|---|
| macAddress | String | Device MAC |
| name | String | Device name |
| rssi | int | Signal strength (RSSI) |
BaseBleDeviceEntity also holds a BaseNanoLinkFrame, which internally stores the device advertising data frames obtained during scanning. You can retrieve it as follows:
val msg01AdvFrame : Msg01AdvFrame? = item.nanoLinkFrame?.let {it as Msg01AdvFrame}
//mac address
val mac = msg01AdvFrame?.macAddress
//deviceName Device name
String deviceName = msg01AdvFrame?.deviceName
//battery Battery percentage. Default: Integer.MIN_VALUE indicates the battery value was not retrieved
val battery = msg01AdvFrame?.battery
//Firmware version number
val firmwareVersion = msg01AdvFrame?.firmwareVersion
//Temperature data
val temperature = msg01AdvFrame?.temperature
//Humidity data
val humidity = msg01AdvFrame?.humidity
//CO2 data
val co2= msg01AdvFrame?.co2
//PM2.5 data
val pm2_5 = msg01AdvFrame?.pm2_5
//PM1.0 data
val pm1_0 = msg01AdvFrame?.pm1_0
//PM10 data
val pm10 = msg01AdvFrame?.pm10
//TVOC data
val voc = msg01AdvFrame?.voc
//HCHO data
val hcho = msg01AdvFrame?.hcho
//PIR data
val humanPresence = msg01AdvFrame?.humanPresence
//Light intensity data
val lightSensing = msg01AdvFrame?.lightSensing
//Atmospheric pressure data
val atmosphericPressure = msg01AdvFrame?.atmosphericPressure
//Noise (SPL) data
val noiseSPL = msg01AdvFrame?.noiseSPL
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
The MSG01 all-in-one air quality sensor device has 1 advertising frame type.
Device frame
- Msg01AdvFrame
| Name | Type | Description |
|---|---|---|
| mac | String | Firmware MAC |
| deviceName | String | Device name |
| firmwareVersion | String | Version number |
| battery | int | Battery percentage. Default: Integer.MIN_VALUE indicates the battery value was not retrieved |
| temperature | Temperature | Temperature private int flag; Level: 0=comfortable, 1=low temperature, 2=high temperature private int value; Value |
| humidity | Humidity | Humidity private int flag; Level: 0=comfortable, 1=low humidity, 2=high humidity private int value; Value |
| co2 | Co2 | CO2 private int flag; Level: 0=excellent, 1=mild, 2=severe private int value; Value |
| pm1_0 | PM1_0 | PM1.0 private int flag; Level: 0=excellent, 1=mild, 2=severe private int value; Value |
| pm2_5 | PM2_5 | PM2.5 private int flag; Level: 0=excellent, 1=mild, 2=severe private int value; Value |
| pm10 | PM10 | PM10 private int flag; Level: 0=excellent, 1=mild, 2=severe private int value; Value |
| voc | Voc | VOC private int flag; Level: 0=excellent, 1=mild, 2=severe private int value; Value |
| hcho | HCHO | HCHO private int flag; Level: 0=excellent, 1=mild, 2=severe private int value; Value |
| humanPresence | HumanPresence | Human presence detection private int flag; private int value; Level: 0=unoccupied, 1=occupied |
| lightSensing | LightSensing | Light intensity private int flag; private int value; Light intensity level: 1, 2, 3, 4, 5 |
| atmosphericPressure | AtmosphericPressure | Atmospheric pressure private int flag; private int value; |
| noiseSPL | NoiseSPL | Noise (SPL) private int flag; private int value; |
# 2. Connection
Generally, stop scanning before connecting. The SDK provides methods to connect and disconnect.
val mBleManager = Msg01BleDevicesManager.getInstance()
//Stop scanning
mBleManager.stopScan(context);
//Connect: module is the device to be connected
val module:MSG01Entity;
mBleManager.connect(context,module)
//Disconnect: macAddress is the device MAC
mBleManager.disConnect(macAddress)
2
3
4
5
6
7
8
Note: Before connecting a device, make sure the device has been scanned. If the device's advertising was not scanned, calling the connect method will fail.
After calling connect(), the SDK monitors the connection process via state callbacks.
//Set listener
mBleManager.setOnConnStateListener {macAddress, connectionState ->
when (it) {
BleConnectionState.Connecting -> {
//This state is called back after connect() is invoked
}
BleConnectionState.Connected -> {
//Preliminary connection succeeded; this is a transitional stage and not yet truly successful
}
BleConnectionState.EnterAuthenticatePassword -> {
}
BleConnectionState.AuthenticateSuccess ->{
}
BleConnectionState.AuthenticateFail ->{
}
BleConnectionState.ConnectComplete -> {
//Device connection complete; only then can you operate the device or navigate to the UI via the methods provided by Msg01BleDevicesManager
}
BleConnectionState.Bond_None -> {
//Device bonding callback; invalid bonding state here
}
BleConnectionState.Bond_Bonding -> {
//Device bonding callback; bonding in progress here
}
BleConnectionState.Bond_Bonded -> {
//Device bonding callback; bonding complete here
//Device bonding complete; only then can you operate the device via the methods provided by 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
During the connection process, the SDK returns multiple connection states to the app; the app must handle them properly.
- BleConnectionState.Connecting, BleConnectionState.Connected: The device is connecting. Do not perform time-consuming operations in these states, because the SDK is performing service discovery and sending authentication data.
- BleConnectionState.Bond_None: Invalid bonding state.
- BleConnectionState.Bond_Bonding: The device is bonding.
- BleConnectionState.Bond_Bonded: Bonding complete. Only then can you operate the device via the methods provided by Msg01BleDevicesManager.
- BleConnectionState.ConnectComplete: The device is connected successfully. You can perform read/write operations, such as configuring advertising parameters and reading history data.
- BleConnectionState.Disconnect: Callback when the connection fails or the device disconnects.
# 3. Device Configuration Read/Write Operations
The device configuration read/write API is as follows, invoked via the Msg01BleDevicesManager.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 the Bluetooth switch is on; only when it is on can you start the phone's CurrentTimeService. If you start it while Bluetooth is off, some Android systems may error. You also need to monitor the Bluetooth switch state: start the service with startSyncTimeServer when Bluetooth turns on, and close it with closeSyncTimeServer when it turns off. Note: when the activity onDestroy is called, you must also close this service with closeSyncTimeServer
val mBleManager = Msg01BleDevicesManager.getInstance()
// Start the service
mBleManager.startSyncTimeServer(context)
// Close the service
mBleManager.closeSyncTimeServer(context)
// Because time sync may occasionally fail on some phones, you need to check whether time sync succeeded; it is recommended to check 3 seconds after the device connects successfully
mBleManager.isDeviceReadSyncTime(context);
// If time sync fails, you need to actively 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 | Version info list |
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
* @param listener 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 info
*/
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
* @param listener 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
* @param listener 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. Set Static Passkey
/**
* Set static passkey
* @param macAddress MAC address
* @param passkey Passkey, 6-digit, range 000000 - 999999
* @param listener Listener
*/
void setStaticPassKey(@NonNull String macAddress, int passkey, OnModifyConfigurationListener listener);
/**
* Modify device passkey
* @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 passkey
* @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.busi_msg01_set_device_password_success_message
else -> R.string.busi_msg01_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
# 6. Query and Set CO2 Sensor Parameters
Co2SensorConfigEntity
| Name | Type | Description |
|---|---|---|
| intvl | int | Sampling interval, in seconds |
| lim | int[] | CO2 threshold range. index=0 is the mild threshold, index=1 is the severe threshold |
| abc | Abc | CO2 sensor configuration |
Abc
| Name | Type | Description |
|---|---|---|
| en | boolean | Whether to enable the CO2 sensor. true=enabled, false=disabled |
| intvl | int | Operating frequency, generally used for zero-point auto-calibration. Range 1~30 days, |
/**
* Query CO2 sensor parameters
* @param macAddress
* @param listener
*/
void getCo2SensorConfig(@NonNull String macAddress, OnQueryResultListener<Co2SensorConfigEntity> listener);
/**
* Set CO2 sensor parameters
* @param macAddress
* @param co2SensorConfigEntity
* @param listener
*/
void setCo2SensorConfig(@NonNull String macAddress, Co2SensorConfigEntity co2SensorConfigEntity, OnModifyConfigurationListener listener);
/**
* Get CO2 sensor configuration
* @return Co2SensorConfigEntity?
*/
suspend fun getCo2SensorConfig(): Co2SensorConfigEntity? = withContext(Dispatchers.Default){
if(connectMacAddress == null){
return@withContext null
}
return@withContext suspendCancellableCoroutine<Co2SensorConfigEntity?> { continuation ->
manager.getCo2SensorConfig(connectMacAddress!!,
OnQueryResultListener<Co2SensorConfigEntity> { _, queryInfo ->
continuation.resume(queryInfo, null)
})
}
}
/**
*Set CO2 sensor configuration
*/
suspend fun setCo2SensorConfig(co2SensorConfigEntity: Co2SensorConfigEntity): Boolean? = withContext(Dispatchers.Default){
if(connectMacAddress == null){
return@withContext null
}
return@withContext suspendCancellableCoroutine<Boolean?> { continuation ->
manager.setCo2SensorConfig(connectMacAddress!!,co2SensorConfigEntity) {
continuation.resume(it, null)
}
}
}
/**
* Get CO2 sensor parameters
*/
private fun getCo2SensorConfig(){
lifecycleScope.launch(Dispatchers.Main){
LoadingDialogUtil.showLoadingDialog()
val result = mConnectedViewModel.getCo2SensorConfig()
LoadingDialogUtil.dismissLoadingDialog()
result?.let {
LogUtil.d("Co2SensorConfig=${it.toString()}")
binding.co2SamplingIntervalTv.text = "${DataUtil.getHour(it.intvl)}h ${DataUtil.getMinute(it.intvl)}m ${DataUtil.getSecond(it.intvl)}s"
when(it.lim != null){
true ->{
when(it.lim.size){
1 -> {
binding.co2GoodSensorThresholdTv.setText("${it.lim[0]}")
binding.co2MildSensorThresholdEdit.setText("${it.lim[0]}")
binding.co2SevereSensorThresholdEdit.setText("")
}
2 ->{
binding.co2GoodSensorThresholdTv.setText("${it.lim[0]}")
binding.co2MildSensorThresholdEdit.setText("${it.lim[0]}")
binding.co2SevereSensorThresholdEdit.setText("${it.lim[1]}")
}
else ->{}
}
binding.switchCo2.isChecked = true
binding.co2AlarmThresholdContentLayout.visibility = View.VISIBLE
}
else ->{
binding.co2GoodSensorThresholdTv.setText("")
binding.co2MildSensorThresholdEdit.setText("")
binding.co2SevereSensorThresholdEdit.setText("")
binding.switchCo2.isChecked = false
binding.co2AlarmThresholdContentLayout.visibility = View.GONE
}
}
}
}
}
/**
* Set CO2 sensor parameters
*/
private fun setCo2SensorConfig(co2SensorConfigEntity: Co2SensorConfigEntity){
lifecycleScope.launch(Dispatchers.Main){
LogUtil.d("setCo2SensorConfig:${co2SensorConfigEntity.toString()}")
LoadingDialogUtil.showLoadingDialog()
val result = mConnectedViewModel.setCo2SensorConfig(co2SensorConfigEntity)
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
# 7. Set CO2 Sensor Single-Point Calibration Parameters
Co2DioxideSensorSinglePointConfig
| Name | Type | Description |
|---|---|---|
| ref | int | Valid range 400 ~ 1500 ppm. |
/**
* Set CO2 sensor parameters
* @param macAddress
* @param carbonDioxideSensorConfiguration
* @param listener
*/
void setCo2DioxideSensorSinglePointConfiguration(@NonNull String macAddress, Co2DioxideSensorSinglePointConfig carbonDioxideSensorConfiguration, OnModifyConfigurationListener listener);
/**
* Set CO2 single-point auto-calibration
* @param enable Boolean Whether to enable single-point auto-calibration
* @param periodDay Int Operating frequency, in days, range 1~30
* @return Boolean
*/
suspend fun setCo2DioxideSensorConfig(co2DioxideSensorConfig: Co2DioxideSensorSinglePointConfig): Boolean = withContext(Dispatchers.Default) {
if (connectMacAddress == null) {
return@withContext false
}
return@withContext suspendCancellableCoroutine<Boolean> { continuation ->
manager.setCo2DioxideSensorSinglePointConfiguration(connectMacAddress!!,co2DioxideSensorConfig) {
continuation.resume(it, null)
}
}
}
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
# 8. Query and Set VOC Sensor Parameters
VocSensorConfigEntity
| Name | Type | Description |
|---|---|---|
| intvl | int | Sampling interval, in seconds |
| lim | int[] | VOC threshold range. index=0 is the mild threshold, index=1 is the severe threshold |
/**
* Set VOC sensor parameters
* @param macAddress
* @param listener
*/
void setVocSensorConfig(@NonNull String macAddress, VocSensorConfigEntity vocSensorConfigEntity, OnModifyConfigurationListener listener);
/**
* Query VOC sensor parameters
* @param macAddress
* @param listener
*/
void getVocSensorConfig(@NonNull String macAddress, OnQueryResultListener<VocSensorConfigEntity> listener);
/**
* Get VOC sensor configuration
* @return VocSensorConfigEntity?
*/
suspend fun getTvocSensorConfig(): VocSensorConfigEntity? = withContext(Dispatchers.Default){
if(connectMacAddress == null){
return@withContext null
}
return@withContext suspendCancellableCoroutine<VocSensorConfigEntity?> { continuation ->
manager.getVocSensorConfig(connectMacAddress!!,
OnQueryResultListener<VocSensorConfigEntity> { _, queryInfo ->
continuation.resume(queryInfo, null)
})
}
}
/**
* Set VOC sensor configuration
*/
suspend fun setTvocSensorConfig(vocSensorConfigEntity: VocSensorConfigEntity): Boolean? = withContext(Dispatchers.Default){
if(connectMacAddress == null){
return@withContext null
}
return@withContext suspendCancellableCoroutine<Boolean?> { continuation ->
manager.setVocSensorConfig(connectMacAddress!!,vocSensorConfigEntity) {
continuation.resume(it, null)
}
}
}
/**
* Get TVOC sensor parameters
*/
private fun getTvocSensorConfig(){
lifecycleScope.launch(Dispatchers.Main){
LoadingDialogUtil.showLoadingDialog()
val result = mConnectedViewModel.getTvocSensorConfig()
LoadingDialogUtil.dismissLoadingDialog()
result?.let {
LogUtil.d("TvocSensorConfig=${it.toString()}")
binding.tvocSamplingIntervalTv.text = "${DataUtil.getHour(it.intvl)}h ${DataUtil.getMinute(it.intvl)}m"
when(it.lim != null){
true ->{
when(it.lim.size){
1 ->{
binding.tvocGoodSensorThresholdTv.setText("${it.lim[0]}")
binding.tvocMildSensorThresholdEdit.setText("${it.lim[0]}")
binding.tvocSevereSensorThresholdEdit.setText("")
}
2 ->{
binding.tvocGoodSensorThresholdTv.setText("${it.lim[0]}")
binding.tvocMildSensorThresholdEdit.setText("${it.lim[0]}")
binding.tvocSevereSensorThresholdEdit.setText("${it.lim[1]}")
}
else ->{}
}
binding.switchTvoc.isChecked = true
binding.tvocAlarmThresholdContentLayout.visibility = View.VISIBLE
}
else ->{
binding.tvocGoodSensorThresholdTv.setText("")
binding.tvocMildSensorThresholdEdit.setText("")
binding.tvocSevereSensorThresholdEdit.setText("")
binding.switchTvoc.isChecked = false
binding.tvocAlarmThresholdContentLayout.visibility = View.GONE
}
}
}
}
}
/**
* Set TVOC sensor parameters
*/
private fun setTvocSensorConfig(vocSensorConfigEntity: VocSensorConfigEntity){
lifecycleScope.launch(Dispatchers.Main){
LogUtil.d("setTvocSensorConfig:${vocSensorConfigEntity.toString()}")
LoadingDialogUtil.showLoadingDialog()
val result = mConnectedViewModel.setTvocSensorConfig(vocSensorConfigEntity)
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
# 9. Query and Set PM Sensor Parameters
PmSensorConfigEntity
| Name | Type | Description |
|---|---|---|
| intvl | int | Sampling interval, in seconds |
| pm1_0 | int[] | PM1.0 threshold range. index=0 is the mild threshold, index=1 is the severe threshold |
| pm2_5 | int[] | PM2.5 threshold range. index=0 is the mild threshold, index=1 is the severe threshold |
| pm10 | int[] | PM10 threshold range. index=0 is the mild threshold, index=1 is the severe threshold |
/**
* Query PM sensor parameters
* @param macAddress
* @param listener
*/
void getPmSensorConfig(@NonNull String macAddress, OnQueryResultListener<PmSensorConfigEntity> listener);
/**
* Set PM sensor parameters
* @param macAddress
* @param pmSensorConfig
* @param listener
*/
void setPmSensorConfig(@NonNull String macAddress, PmSensorConfigEntity pmSensorConfig, OnModifyConfigurationListener listener);
/**
* Get PM sensor configuration
* @return PmSensorConfigEntity?
*/
suspend fun getPmSensorConfig(): PmSensorConfigEntity? = withContext(Dispatchers.Default){
if(connectMacAddress == null){
return@withContext null
}
return@withContext suspendCancellableCoroutine<PmSensorConfigEntity?> { continuation ->
manager.getPmSensorConfig(connectMacAddress!!,
OnQueryResultListener<PmSensorConfigEntity> { _, queryInfo ->
continuation.resume(queryInfo, null)
})
}
}
/**
* Set PM sensor configuration
*/
suspend fun setPmSensorConfig(pmSensorConfigEntity: PmSensorConfigEntity): Boolean? = withContext(Dispatchers.Default){
if(connectMacAddress == null){
return@withContext null
}
return@withContext suspendCancellableCoroutine<Boolean?> { continuation ->
manager.setPmSensorConfig(connectMacAddress!!,pmSensorConfigEntity) {
continuation.resume(it, null)
}
}
}
/**
* Get PM sensor parameters
*/
private fun getPmSensorConfig(){
lifecycleScope.launch(Dispatchers.Main){
LoadingDialogUtil.showLoadingDialog()
val result = mConnectedViewModel.getPmSensorConfig()
LoadingDialogUtil.dismissLoadingDialog()
result?.let {
LogUtil.d("PmSensorConfig=${it.toString()}")
binding.pm25SamplingIntervalTv.text = "${DataUtil.getHour(it.intvl)}h ${DataUtil.getMinute(it.intvl)}m"
when(it.pm2_5 != null){
true ->{
when(it.pm2_5.size){
1 ->{
binding.pm25GoodSensorThresholdTv.setText("${it.pm2_5[0]}")
binding.pm25MildSensorThresholdEdit.setText("${it.pm2_5[0]}")
binding.pm25SevereSensorThresholdEdit.setText("")
}
2 ->{
binding.pm25GoodSensorThresholdTv.setText("${it.pm2_5[0]}")
binding.pm25MildSensorThresholdEdit.setText("${it.pm2_5[0]}")
binding.pm25SevereSensorThresholdEdit.setText("${it.pm2_5[1]}")
}
else ->{}
}
binding.switchPm25.isChecked = true
binding.pm25AlarmThresholdContentLayout.visibility = View.VISIBLE
}
else ->{
binding.pm25GoodSensorThresholdTv.setText("")
binding.pm25MildSensorThresholdEdit.setText("")
binding.pm25SevereSensorThresholdEdit.setText("")
binding.switchPm25.isChecked = false
binding.pm25AlarmThresholdContentLayout.visibility = View.GONE
}
}
when(it.pm10 != null){
true ->{
when(it.pm10.size){
1 ->{
binding.pm10GoodSensorThresholdTv.setText("${it.pm10[0]}")
binding.pm10MildSensorThresholdEdit.setText("${it.pm10[0]}")
binding.pm10SevereSensorThresholdEdit.setText("")
}
2 ->{
binding.pm10GoodSensorThresholdTv.setText("${it.pm10[0]}")
binding.pm10MildSensorThresholdEdit.setText("${it.pm10[0]}")
binding.pm10SevereSensorThresholdEdit.setText("${it.pm10[1]}")
}
else ->{}
}
binding.switchPm10.isChecked = true
binding.pm10AlarmThresholdContentLayout.visibility = View.VISIBLE
}
else ->{
binding.pm10GoodSensorThresholdTv.setText("")
binding.pm10MildSensorThresholdEdit.setText("")
binding.pm10SevereSensorThresholdEdit.setText("")
binding.switchPm10.isChecked = false
binding.pm10AlarmThresholdContentLayout.visibility = View.GONE
}
}
}
}
}
/**
* Set PM sensor parameters
*/
private fun setPmSensorConfig(pmSensorConfigEntity: PmSensorConfigEntity){
lifecycleScope.launch(Dispatchers.Main){
LogUtil.d("setPmSensorConfig:${pmSensorConfigEntity.toString()}")
LoadingDialogUtil.showLoadingDialog()
val result = mConnectedViewModel.setPmSensorConfig(pmSensorConfigEntity)
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
# 10. Query and Set HCHO Sensor Parameters
HCHOSensorConfigEntity
| Name | Type | Description |
|---|---|---|
| intvl | int | Sampling interval, in seconds |
| lim | int[] | Threshold range. index=0 is the mild threshold, index=1 is the severe threshold |
/**
* Query HCHO sensor parameters
* @param macAddress
* @param listener
*/
void getHchoSensorConfig(@NonNull String macAddress, OnQueryResultListener<HCHOSensorConfigEntity> listener);
/**
* Set HCHO sensor parameters
* @param macAddress
* @param hchoSensorConfig
* @param listener
*/
void setHchoSensorConfig(@NonNull String macAddress, HCHOSensorConfigEntity hchoSensorConfig, OnModifyConfigurationListener listener);
/**
* Get HCHO sensor configuration
* @return HCHOSensorConfigEntity?
*/
suspend fun getHchoSensorConfig(): HCHOSensorConfigEntity? = withContext(Dispatchers.Default){
if(connectMacAddress == null){
return@withContext null
}
return@withContext suspendCancellableCoroutine<HCHOSensorConfigEntity?> { continuation ->
manager.getHchoSensorConfig(connectMacAddress!!,
OnQueryResultListener<HCHOSensorConfigEntity> { _, queryInfo ->
continuation.resume(queryInfo, null)
})
}
}
/**
* Set HCHO sensor configuration
*/
suspend fun setHchoSensorConfig(hchoSensorConfigEntity: HCHOSensorConfigEntity): Boolean? = withContext(Dispatchers.Default){
if(connectMacAddress == null){
return@withContext null
}
return@withContext suspendCancellableCoroutine<Boolean?> { continuation ->
manager.setHchoSensorConfig(connectMacAddress!!,hchoSensorConfigEntity) {
continuation.resume(it, null)
}
}
}
/**
* Get HCHO sensor parameters
*/
private fun getHchoSensorConfig(){
lifecycleScope.launch(Dispatchers.Main){
LoadingDialogUtil.showLoadingDialog()
val result = mConnectedViewModel.getHchoSensorConfig()
LoadingDialogUtil.dismissLoadingDialog()
result?.let {
LogUtil.d("HchoSensorConfig=${it.toString()}")
binding.hchoSamplingIntervalTv.text = "${DataUtil.getForDayInDay(it.intvl)}d ${DataUtil.getForDayInHour(it.intvl)}h"
when(it.lim != null ){
true ->{
when(it.lim.size){
1 ->{
binding.hchoGoodSensorThresholdTv.setText("${BigDecimal(it.lim[0]).divide(
BigDecimal(1000)).setScale(3).toDouble()}")
binding.hchoMildSensorThresholdEdit.setText("${BigDecimal(it.lim[0]).divide(
BigDecimal(1000)).setScale(3).toDouble()}")
binding.hchoSevereSensorThresholdEdit.setText("")
}
2 ->{
binding.hchoGoodSensorThresholdTv.setText("${BigDecimal(it.lim[0]).divide(
BigDecimal(1000)).setScale(3).toDouble()}")
binding.hchoMildSensorThresholdEdit.setText("${BigDecimal(it.lim[0]).divide(
BigDecimal(1000)).setScale(3).toDouble()}")
binding.hchoSevereSensorThresholdEdit.setText("${BigDecimal(it.lim[1]).divide(
BigDecimal(1000)).setScale(3).toDouble()}")
}
else ->{}
}
binding.switchHcho.isChecked = true
binding.hchoAlarmThresholdContentLayout.visibility = View.VISIBLE
}
else ->{
binding.hchoGoodSensorThresholdTv.setText("")
binding.hchoMildSensorThresholdEdit.setText("")
binding.hchoSevereSensorThresholdEdit.setText("")
binding.switchHcho.isChecked = false
binding.hchoAlarmThresholdContentLayout.visibility = View.GONE
}
}
}
}
}
/**
* Set HCHO sensor parameters
*/
private fun setHchoSensorConfig(hchoSensorConfigEntity: HCHOSensorConfigEntity){
lifecycleScope.launch(Dispatchers.Main){
LogUtil.d("setHchoSensorConfig:${hchoSensorConfigEntity.toString()}")
LoadingDialogUtil.showLoadingDialog()
val result = mConnectedViewModel.setHchoSensorConfig(hchoSensorConfigEntity)
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
# 11. Query HT Sensor Parameters
HTSensorConfigEntity
| Name | Type | Description |
|---|---|---|
| intvl | int | Temperature & humidity sampling interval, in seconds |
| t_ref | HtRef | Temperature threshold parameter. t_ref=null indicates that temperature acquisition is not enabled |
| h_ref | HtRef | Humidity threshold parameter. h_ref=null indicates that humidity acquisition is not enabled |
HtRef
| Name | Type | Description |
|---|---|---|
| min | int | Minimum threshold. Default value=Integer.MIN_VALUE. The format is x100; for display, use value/100f. To configure, multiply by x100 |
| max | int | Maximum threshold. Default value=Integer.MIN_VALUE. The format is x100; for display, use value/100f. To configure, multiply by x100 |
/**
* Query HT sensor parameters
* @param macAddress
* @param listener
*/
void getHtSensorConfig(@NonNull String macAddress, OnQueryResultListener<HTSensorConfigEntity> listener);
/**
* Set HT sensor parameters
* @param macAddress
* @param htSensorConfig
* @param listener
*/
void setHtSensorConfig(@NonNull String macAddress, HTSensorConfigEntity htSensorConfig, OnModifyConfigurationListener listener);
/**
* Get HT sensor configuration
* @return HCHOSensorConfigEntity?
*/
suspend fun getHTSensorConfig(): HTSensorConfigEntity? = withContext(Dispatchers.Default){
if(connectMacAddress == null){
return@withContext null
}
return@withContext suspendCancellableCoroutine<HTSensorConfigEntity?> { continuation ->
manager.getHtSensorConfig(connectMacAddress!!,
OnQueryResultListener<HTSensorConfigEntity> { _, queryInfo ->
continuation.resume(queryInfo, null)
})
}
}
/**
* Set HT sensor configuration
* @return Boolean?
*/
suspend fun setHTSensorConfig(htSensorConfigEntity: HTSensorConfigEntity): Boolean? = withContext(Dispatchers.Default){
if(connectMacAddress == null){
return@withContext null
}
return@withContext suspendCancellableCoroutine<Boolean?> { continuation ->
manager.setHtSensorConfig(connectMacAddress!!,htSensorConfigEntity) {
continuation.resume(it, null)
}
}
}
/**
* Get HT sensor parameters
*/
private fun getHTSensorConfig(){
lifecycleScope.launch(Dispatchers.Main){
LoadingDialogUtil.showLoadingDialog()
val result = mConnectedViewModel.getHTSensorConfig()
LoadingDialogUtil.dismissLoadingDialog()
result?.let {
binding.htSamplingIntervalTv.text = "${DataUtil.getHour(it.intvl)}h ${DataUtil.getMinute(it.intvl)}m ${DataUtil.getSecond(it.intvl)}s"
when(it.t_ref != null){
true ->{
if(it.t_ref?.min != Int.MIN_VALUE){
binding.temperatureLowSensorThresholdEdit.setText("${(it.t_ref?.min?:0)/100f}")
}
if(it.t_ref?.max != Int.MIN_VALUE){
binding.temperatureHighSensorThresholdEdit.setText("${(it.t_ref?.max?:45)/100f}")
}
binding.switchTemperature.isChecked = true
binding.temperatureAlarmThresholdContentLayout.visibility = View.VISIBLE
}
else ->{
binding.switchTemperature.isChecked = false
binding.temperatureAlarmThresholdContentLayout.visibility = View.GONE
}
}
when(it.h_ref != null){
true ->{
if(it.h_ref?.min != Int.MIN_VALUE){
binding.humidityLowSensorThresholdEdit.setText("${(it.h_ref?.min?:0)/100f}")
}
if(it.h_ref?.max != Int.MIN_VALUE){
binding.humidityHighSensorThresholdEdit.setText("${(it.h_ref?.max?:45)/100f}")
}
binding.switchHumidity.isChecked = true
binding.humidityAlarmThresholdContentLayout.visibility = View.VISIBLE
}
else ->{
binding.switchHumidity.isChecked = false
binding.humidityAlarmThresholdContentLayout.visibility = View.GONE
}
}
}
}
}
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
# 12. Query and Set Advertising Parameters
AdvConfigEntity
| Name | Type | Description |
|---|---|---|
| intvl | int | Advertising interval. Range 100ms ~ 10000ms, step 100ms |
| txpwr | int | Advertising power (TX power). There are 8 advertising power levels: -40dBm, -20dBm, -16dBm, -12dBm, -8dBm, -4dBm, 0dBm, 4dBm, 8dBm |
/**
* Read advertising parameters
* @param macAddress
* @param listener
*/
void getAdvConfig(@NonNull String macAddress, OnQueryResultListener<AdvConfigEntity> listener);
/**
* Set advertising parameters
* @param macAddress
* @param advConfigEntity
* @param listener
*/
void setAdvConfig(@NonNull String macAddress, AdvConfigEntity advConfigEntity, OnModifyConfigurationListener listener);
/**
* Get advertising parameters
* @return AdvConfigEntity?
*/
suspend fun getAdvParams(): AdvConfigEntity? = withContext(Dispatchers.Default){
if(connectMacAddress == null){
return@withContext null
}
return@withContext suspendCancellableCoroutine<AdvConfigEntity?> { continuation ->
manager.getAdvConfig(connectMacAddress!!,
OnQueryResultListener<AdvConfigEntity> { _, queryInfo ->
continuation.resume(queryInfo, null)
})
}
}
/**
* Set advertising parameters
* @return Boolean
*/
suspend fun setAdvParams(advConfigEntity: AdvConfigEntity): Boolean = withContext(Dispatchers.Default) {
if (connectMacAddress == null) {
return@withContext false
}
return@withContext suspendCancellableCoroutine<Boolean> { continuation ->
manager.setAdvConfig(connectMacAddress!!,advConfigEntity) {
continuation.resume(it, null)
}
}
}
/**
* Query advertising parameters
*/
private fun getAdvConfiguration() {
lifecycleScope.launch(Dispatchers.Main) {
LoadingDialogUtil.showLoadingDialog()
val advParams =mConnectedViewModel.getAdvParams()
LoadingDialogUtil.dismissLoadingDialog()
advParams?.let {
mAdvConfigEntity = it
binding.advIntervalEdit.setText("${it.intvl}")
binding.advIntervalEdit.setSelection(binding.advIntervalEdit.text.toString().length)
binding.advIntervalSeekbar.progress =
intervalRange.indexOf(it.intvl / intervalStep)
binding.advTxPowerTv.text = "${it.txpwr} dBm"
binding.advTxPowerSeekbar.progress = powerRange.indexOf(it.txpwr)
}
}
}
/**
* Set relay-frame advertising parameters
*/
private fun setAdvertisingParametersConfiguration() {
mAdvConfigEntity?.let {
lifecycleScope.launch(Dispatchers.Main) {
LoadingDialogUtil.showLoadingDialog()
val result = mConnectedViewModel.setAdvParams(it)
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
# 13. Read and Set Alarm Light Effect Parameters
AlarmLightEffectConfigEntity
| Name | Type | Description |
|---|---|---|
| en | boolean | true=on, false=off |
/**
* Read alarm light effect parameters
* @param macAddress
* @param listener
*/
void getAlarmLightEffectConfig(@NonNull String macAddress, OnQueryResultListener<AlarmLightEffectConfigEntity> listener);
/**
* Set alarm light effect parameters
* @param macAddress
* @param alarmLightEffectConfig
* @param listener
*/
void setAlarmLightEffectConfig(@NonNull String macAddress, AlarmLightEffectConfigEntity alarmLightEffectConfig, OnModifyConfigurationListener listener);
/**
* Query alarm light effect configuration
* @return AlarmLightEffectConfigEntity?
*/
suspend fun getAlarmLightEffect(): AlarmLightEffectConfigEntity? = withContext(Dispatchers.Default){
if(connectMacAddress == null){
return@withContext null
}
return@withContext suspendCancellableCoroutine<AlarmLightEffectConfigEntity?> { continuation ->
manager.getAlarmLightEffectConfig(connectMacAddress!!,
OnQueryResultListener<AlarmLightEffectConfigEntity> { _, queryInfo ->
continuation.resume(queryInfo, null)
})
}
}
/**
* Set alarm light effect configuration
* @return Boolean
*/
suspend fun setAlarmLightEffect(alarmLightEffectConfigEntity: AlarmLightEffectConfigEntity): Boolean = withContext(Dispatchers.Default) {
if (connectMacAddress == null) {
return@withContext false
}
return@withContext suspendCancellableCoroutine<Boolean> { continuation ->
manager.setAlarmLightEffectConfig(connectMacAddress!!,alarmLightEffectConfigEntity) {
continuation.resume(it, null)
}
}
}
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
# 14. Query Light Sensor Parameters
LightSensorConfigEntity
| Name | Type | Description |
|---|---|---|
| intvl | int | Sampling interval, in seconds |
/**
* Query light sensor parameters
* @param macAddress
* @param listener
*/
void getLightSensorConfig(@NonNull String macAddress, OnQueryResultListener<LightSensorConfigEntity> listener);
/**
* Set light sensor parameters
* @param macAddress
* @param lightSensorConfig
* @param listener
*/
void setLightSensorConfig(@NonNull String macAddress, LightSensorConfigEntity lightSensorConfig, OnModifyConfigurationListener listener);
/**
* Get light sensor configuration
* @return LightSensorConfigEntity?
*/
suspend fun getLightSensorConfig(): LightSensorConfigEntity? = withContext(Dispatchers.Default){
if(connectMacAddress == null){
return@withContext null
}
return@withContext suspendCancellableCoroutine<LightSensorConfigEntity?> { continuation ->
manager.getLightSensorConfig(connectMacAddress!!,
OnQueryResultListener<LightSensorConfigEntity> { _, queryInfo ->
continuation.resume(queryInfo, null)
})
}
}
/**
* Set light sensor configuration
* @return lightSensorConfigEntity?
*/
suspend fun setLightSensorConfig(lightSensorConfigEntity: LightSensorConfigEntity): Boolean? = withContext(Dispatchers.Default){
if(connectMacAddress == null){
return@withContext null
}
return@withContext suspendCancellableCoroutine<Boolean?> { continuation ->
manager.setLightSensorConfig(connectMacAddress!!,lightSensorConfigEntity) {
continuation.resume(it, null)
}
}
}
/**
* Get light sensor parameters
*/
private fun getLightSensorConfig(){
lifecycleScope.launch(Dispatchers.Main){
LoadingDialogUtil.showLoadingDialog()
val result = mConnectedViewModel.getLightSensorConfig()
LoadingDialogUtil.dismissLoadingDialog()
result?.let {
LogUtil.d("LightSensorConfig=${it.toString()}")
mLightSensorConfig = it
mLightSamplingInterval = it.intvl
binding.lightSensorSamplingIntervalTv.text = "${DataUtil.getHour(it.intvl)}h ${DataUtil.getMinute(it.intvl)}m ${DataUtil.getSecond(it.intvl)}s"
}
}
}
/**
* Set light sensor parameters
*/
private fun setLightSensorConfig(lightSensorConfigEntity: LightSensorConfigEntity){
lifecycleScope.launch(Dispatchers.Main){
LogUtil.d("setLightSensorConfig:${lightSensorConfigEntity.toString()}")
LoadingDialogUtil.showLoadingDialog()
val result = mConnectedViewModel.setLightSensorConfig(lightSensorConfigEntity)
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
# 15. Query Display Language Parameters
DisplayLanguageConfig
| Name | Type | Description |
|---|---|---|
| lang | String | Language. Chinese="zh-CN", English="en" |
/**
* Query display language parameters
* @param macAddress
* @param listener
*/
void getDisplayLanguageConfig(@NonNull String macAddress, OnQueryResultListener<DisplayLanguageConfig> listener);
/**
* Set display language parameters
* @param macAddress
* @param displayLanguageConfig
* @param listener
*/
void setDisplayLanguageConfig(@NonNull String macAddress, DisplayLanguageConfig displayLanguageConfig, OnModifyConfigurationListener listener);
/**
* Get the device's screen display language
* @return DisplayLanguageConfig?
*/
suspend fun getScreenLanguage(): DisplayLanguageConfig? = withContext(Dispatchers.Default){
if(connectMacAddress == null){
return@withContext null
}
return@withContext suspendCancellableCoroutine<DisplayLanguageConfig?> { continuation ->
manager.getDisplayLanguageConfig(connectMacAddress!!,
OnQueryResultListener<DisplayLanguageConfig> { _, queryInfo ->
continuation.resume(queryInfo, null)
})
}
}
/**
* Set the device's screen display language
* @param language
* @return Boolean
*
*/
suspend fun setScreenLanguage(language: DisplayLanguageConfig): Boolean = withContext(Dispatchers.Default) {
if (connectMacAddress == null) {
return@withContext false
}
return@withContext suspendCancellableCoroutine<Boolean> { continuation ->
manager.setDisplayLanguageConfig(connectMacAddress!!,language) {
continuation.resume(it, null)
}
}
}
/**
* Get screen language
*/
private fun getDisplayLanguageConfig(){
lifecycleScope.launch(Dispatchers.Main){
LoadingDialogUtil.showLoadingDialog()
val result = mConnectedViewModel.getScreenLanguage()
LoadingDialogUtil.dismissLoadingDialog()
result?.let {
when (it.lang) {
LANGUAGE_CHINESE -> {
selectLanguage(LANGUAGE_CHINESE)
}
LANGUAGE_ENGLISH -> {
selectLanguage(LANGUAGE_ENGLISH)
}
else -> {
selectLanguage(LANGUAGE_CHINESE)
}
}
}
}
}
/**
* Set screen language
*/
private fun setScreenLanguage() {
val config = (displayLanguageConfig?:DisplayLanguageConfig()).apply {
this.lang = mSelectedLanguage
}
lifecycleScope.launch(Dispatchers.Main) {
LoadingDialogUtil.showLoadingDialog()
val result = mConnectedViewModel.setScreenLanguage(config)
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
# 16. Firmware Upgrade.
/**
* Firmware upgrade
* @param context Context
* @param macAddress MAC address
* @param isLinkUpgrade Whether it is a connection-based upgrade or OTA upgrade. 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 Firmware upgrade target. Default target="main"; for URL upgrade, target="app"
* @param listener
*/
void firmwareUpgrade(@NonNull Context context,@NonNull String macAddress, boolean isLinkUpgrade, String filePath, @NonNull byte[] upgradeData, String target, OnFirmwareUpgradeListener listener) ;
/**
* Modulate to the system file
*/
private fun gotoSystemFilePage() {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
intent.type = "*/*"
launcherActivityResult.launch(intent)
}
//Read the firmware upgrade 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 ->
handleFileByQ(uri)
}
}
}
private fun handleFile(fileUri: Uri) {
//Get the archive file name
var fileName = ""
//Get the archive 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 4k 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; the device will actively disconnect from the phone, so the OnConnStateListener callback will be triggered
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
# 17. Query History Records
History data can be queried for 5 types of data: CO2, laser particulate matter, VOC, temperature & humidity, and HCHO.
/**
* Get the device's 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 phone RAM limits.
* @param startTime Long Start time of the query, in seconds
* @param endTime Long End time of the query, in seconds
* @param systemTime Long Current phone system time, in seconds. Recommended to pass System.currentTimeMillis()/1000 directly
*/
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.Humidity_And_Temperature_x100 -> StorageIndexRecordSearcher<StorageActualDataHumidityAndTemperatureX100>()
StorageDataBlockType.Laser_Particle-> StorageIndexRecordSearcher<StorageActualDataLaseParticel>()
StorageDataBlockType.VOC-> StorageIndexRecordSearcher<StorageActualDataVoc>()
StorageDataBlockType.CO2-> StorageIndexRecordSearcher<StorageActualDataCo2>()
StorageDataBlockType.HCHO-> StorageIndexRecordSearcher<StorageActualDataHcho>()
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()
){
LogUtil.e("StorageIndexRecordSearcher ${resultList.size}")
when(sensorType){
StorageDataBlockType.Humidity_And_Temperature_x100 -> {
HtSensorHistoryDataActivity.htResultList = resultList as List<StorageActualDataHumidityAndTemperatureX100>
}
StorageDataBlockType.Laser_Particle-> {
PmSensorHistoryDataActivity.pmResultList = resultList as List<StorageActualDataLaseParticel>
}
StorageDataBlockType.VOC-> {
TvocSensorHistoryDataActivity.vocResultList = resultList as List<StorageActualDataVoc>
}
StorageDataBlockType.CO2-> {
Co2SensorHistoryDataActivity.co2ResultList = resultList as List<StorageActualDataCo2>
}
StorageDataBlockType.HCHO-> {
HchoSensorHistoryDataActivity.hchoResultList = resultList as List<StorageActualDataHcho>
}
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
65
66
67
68
69
70
# 18. Clear History Data
/**
* Clear history data
* @param macAddress
* @param force Clear data when force=true
* @param listener
*/
void clearHistory(@NonNull String macAddress, boolean force, OnModifyConfigurationListener listener);
/**
* Set clear history 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 history data
*/
private fun cleanHistoryData(){
lifecycleScope.launch(Dispatchers.Main){
LoadingDialogUtil.showLoadingDialog()
val result = mConnectedViewModel.clearHistory( true)
LoadingDialogUtil.dismissLoadingDialog()
ToastUtils.showShort(when(result){
true -> R.string.busi_msg01_clear_successfully
else -> R.string.busi_msg01_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
# 19. 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 of the nanolink protocol |
/**
* Get the list of commands supported by the device
* @param macAddress MAC address
* @param listener 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 History
- 2026/08/20 Added the basic MSG01 device operation APIs