# 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
       }
   }
1
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'
1
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']
           }
       }
   }
1
2
3
4
5
6
7
8
9
10
11
12

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" />
1
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)
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

The MSG01 all-in-one air quality sensor device has 1 advertising frame type.

  1. 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)
1
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 -> {}
            }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

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);


1
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}"
               }
           }

       }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# 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()
           }
       }

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

# 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()
           }

       }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# 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
               })
           }
       }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# 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))

               }
           }
       }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
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)
               }
           }
       }



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 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))

               }
           }
       }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108

# 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))

               }
           }
       }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
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))

                }
            }
        }










1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
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
                        }
                    }
                }
            }
        }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97

# 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))

                }
                }
            }

        }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91

# 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)
                }
            }
        }

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

# 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))

                }
            }
        }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82

# 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))
                }
            }
        }




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96

# 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()
                    }
                })
        }



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
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{

                            }
                        }
                    }
                }
            }
        }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70

# 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
            })
        }

    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# 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)
                })
        }
    }
1
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
Last Updated:: 9/24/2026, 4:17:45 PM