Skip to content

Commit 1279092

Browse files
JasonLiuZhuoChengrestyled-commits
authored andcommitted
Cluster command detail page (#11354)
* try to find a state where m5stack doesn't bootloop * add back class description * select different cluster, command will remove previous displayed parameter * add responseValueInfo class instead of string split * remove unused variable * Restyled by whitespace * Restyled by google-java-format * Restyled by gn * fix parameter response ui alignment issue * resolve comments * fix format * regenerate clusterInfoMapping.java * resolve comments * merge with master to get pass the check Co-authored-by: Restyled.io <[email protected]>
1 parent 84ace80 commit 1279092

File tree

12 files changed

+768
-287
lines changed

12 files changed

+768
-287
lines changed

src/android/CHIPTool/.idea/misc.xml

-9
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,95 @@
11
package com.google.chip.chiptool.clusterclient.clusterinteraction
22

33
import android.os.Bundle
4+
import android.util.Log
45
import android.view.LayoutInflater
56
import android.view.View
67
import android.view.ViewGroup
8+
import android.widget.ArrayAdapter
9+
import android.widget.AutoCompleteTextView
10+
import android.widget.LinearLayout
711
import android.widget.Toast
12+
import androidx.constraintlayout.widget.ConstraintLayout
13+
import androidx.core.view.forEach
814
import androidx.fragment.app.Fragment
9-
import androidx.lifecycle.lifecycleScope
15+
import chip.clusterinfo.ClusterCommandCallback
16+
import chip.clusterinfo.ClusterInfo
17+
import chip.clusterinfo.CommandInfo
18+
import chip.clusterinfo.CommandResponseInfo
19+
import chip.clusterinfo.DelegatedClusterCallback
20+
import chip.devicecontroller.ChipClusters
1021
import chip.devicecontroller.ChipDeviceController
22+
import chip.devicecontroller.ClusterInfoMapping
1123
import com.google.chip.chiptool.ChipClient
1224
import com.google.chip.chiptool.GenericChipDeviceListener
1325
import com.google.chip.chiptool.R
26+
import kotlinx.android.synthetic.main.cluster_callback_item.view.clusterCallbackDataTv
27+
import kotlinx.android.synthetic.main.cluster_callback_item.view.clusterCallbackNameTv
28+
import kotlinx.android.synthetic.main.cluster_callback_item.view.clusterCallbackTypeTv
29+
import kotlinx.android.synthetic.main.cluster_detail_fragment.view.callbackList
30+
import kotlinx.android.synthetic.main.cluster_detail_fragment.view.clusterAutoCompleteTv
31+
import kotlinx.android.synthetic.main.cluster_detail_fragment.view.commandAutoCompleteTv
32+
import kotlinx.android.synthetic.main.cluster_detail_fragment.view.invokeCommand
33+
import kotlinx.android.synthetic.main.cluster_detail_fragment.view.parameterList
34+
import kotlinx.android.synthetic.main.cluster_parameter_item.view.clusterParameterData
35+
import kotlinx.android.synthetic.main.cluster_parameter_item.view.clusterParameterNameTv
36+
import kotlinx.android.synthetic.main.cluster_parameter_item.view.clusterParameterTypeTv
1437
import kotlinx.coroutines.CoroutineScope
38+
import kotlinx.coroutines.Dispatchers
39+
import kotlinx.coroutines.Job
40+
import kotlinx.coroutines.cancel
1541

1642
/**
1743
* ClusterDetailFragment allows user to pick cluster, command, specify parameters and see
1844
* the callback result.
1945
*/
20-
class ClusterDetailFragment : Fragment(){
46+
class ClusterDetailFragment : Fragment() {
2147
private val deviceController: ChipDeviceController
2248
get() = ChipClient.getDeviceController(requireContext())
2349

24-
private lateinit var scope: CoroutineScope
50+
private val scope = CoroutineScope(Dispatchers.Main + Job())
51+
private lateinit var clusterMap: Map<String, ClusterInfo>
52+
private lateinit var selectedClusterInfo: ClusterInfo
53+
private lateinit var selectedCluster: ChipClusters.BaseChipCluster
54+
private lateinit var selectedCommandCallback: DelegatedClusterCallback
55+
private lateinit var selectedCommandInfo: CommandInfo
56+
private var devicePtr = 0L
57+
private var endpointId = 0
2558

2659
override fun onCreateView(
2760
inflater: LayoutInflater,
2861
container: ViewGroup?,
2962
savedInstanceState: Bundle?
3063
): View {
31-
scope = viewLifecycleOwner.lifecycleScope
32-
64+
clusterMap = ClusterInfoMapping().clusterMap
65+
devicePtr = checkNotNull(requireArguments().getLong(DEVICE_PTR_KEY))
66+
endpointId = checkNotNull(requireArguments().getInt(ENDPOINT_ID_KEY))
3367
return inflater.inflate(R.layout.cluster_detail_fragment, container, false).apply {
3468
deviceController.setCompletionListener(GenericChipDeviceListener())
69+
commandAutoCompleteTv.visibility = View.GONE
70+
clusterAutoCompleteSetup(clusterAutoCompleteTv, commandAutoCompleteTv, parameterList)
71+
commandAutoCompleteSetup(commandAutoCompleteTv, inflater, parameterList, callbackList)
72+
invokeCommand.setOnClickListener {
73+
val commandArguments = HashMap<String, Any>()
74+
parameterList.forEach {
75+
val type =
76+
selectedCommandInfo.commandParameters[it.clusterParameterNameTv.text.toString()]!!.type
77+
val data = castStringToType(it.clusterParameterData.text.toString(), type)!!
78+
79+
commandArguments[it.clusterParameterNameTv.text.toString()] = data
80+
}
81+
selectedCommandInfo.getCommandFunction()
82+
.invokeCommand(selectedCluster, selectedCommandCallback, commandArguments)
83+
}
84+
}
85+
}
86+
87+
private fun castStringToType(data: String, type: Class<*>): Any? {
88+
return when (type) {
89+
Int::class.java -> data.toInt()
90+
String::class.java -> data
91+
Boolean::class.java -> data.toBoolean()
92+
else -> null
3593
}
3694
}
3795

@@ -41,8 +99,119 @@ class ClusterDetailFragment : Fragment(){
4199
}
42100
}
43101

102+
private fun clusterAutoCompleteSetup(
103+
clusterAutoComplete: AutoCompleteTextView,
104+
commandAutoComplete: AutoCompleteTextView,
105+
parameterList: LinearLayout
106+
) {
107+
val clusterNameList = constructHint(clusterMap)
108+
val clusterAdapter =
109+
ArrayAdapter(requireContext(), android.R.layout.simple_list_item_1, clusterNameList)
110+
clusterAutoComplete.setAdapter(clusterAdapter)
111+
clusterAutoComplete.setOnItemClickListener { parent, view, position, id ->
112+
commandAutoComplete.visibility = View.VISIBLE
113+
// when new cluster is selected, clear the command text and possible parameterList
114+
commandAutoComplete.setText("", false)
115+
parameterList.removeAllViews()
116+
// populate all the commands that belong to the selected cluster
117+
val selectedCluster: String = clusterAutoComplete.adapter.getItem(position).toString()
118+
val commandAdapter = getCommandOptions(selectedCluster)
119+
commandAutoComplete.setAdapter(commandAdapter)
120+
}
121+
}
122+
123+
private fun commandAutoCompleteSetup(
124+
commandAutoComplete: AutoCompleteTextView,
125+
inflater: LayoutInflater,
126+
parameterList: LinearLayout,
127+
callbackList: LinearLayout
128+
) {
129+
commandAutoComplete.setOnItemClickListener { parent, view, position, id ->
130+
// when new command is selected, clear all the parameterList
131+
parameterList.removeAllViews()
132+
selectedCluster = selectedClusterInfo.createClusterFunction.create(devicePtr, endpointId)
133+
val selectedCommand: String = commandAutoComplete.adapter.getItem(position).toString()
134+
selectedCommandInfo = selectedClusterInfo.commands[selectedCommand]!!
135+
selectedCommandCallback = selectedCommandInfo.commandCallbackSupplier.get()
136+
populateCommandParameter(inflater, parameterList)
137+
selectedCommandCallback.setCallbackDelegate(object : ClusterCommandCallback {
138+
override fun onSuccess(responseValues: Map<CommandResponseInfo, Any>) {
139+
showMessage("Command success")
140+
// Populate UI based on response values. We know the types from CommandInfo.getCommandResponses().
141+
requireActivity().runOnUiThread {
142+
populateCallbackResult(
143+
responseValues,
144+
inflater,
145+
callbackList
146+
)
147+
}
148+
responseValues.forEach { Log.d(TAG, it.toString()) }
149+
}
150+
151+
override fun onFailure(exception: Exception) {
152+
showMessage("Command failed")
153+
Log.e(TAG, exception.toString())
154+
}
155+
})
156+
}
157+
}
158+
159+
private fun populateCommandParameter(inflater: LayoutInflater, parameterList: LinearLayout) {
160+
selectedCommandInfo.commandParameters.forEach { (paramName, paramInfo) ->
161+
val param = inflater.inflate(R.layout.cluster_parameter_item, null, false) as ConstraintLayout
162+
param.clusterParameterNameTv.text = "${paramName}"
163+
param.clusterParameterTypeTv.text = "${paramInfo.type}"
164+
parameterList.addView(param)
165+
}
166+
}
167+
168+
private fun populateCallbackResult(
169+
responseValues: Map<CommandResponseInfo, Any>,
170+
inflater: LayoutInflater,
171+
callbackList: LinearLayout
172+
) {
173+
responseValues.forEach { (variableNameType, response) ->
174+
val callback =
175+
inflater.inflate(R.layout.cluster_callback_item, null, false) as ConstraintLayout
176+
callback.clusterCallbackNameTv.text = variableNameType.name
177+
callback.clusterCallbackDataTv.text = response.toString()
178+
callback.clusterCallbackTypeTv.text = variableNameType.type
179+
callbackList.addView(callback)
180+
}
181+
}
182+
183+
private fun getCommandOptions(
184+
clusterName: String
185+
): ArrayAdapter<String> {
186+
selectedClusterInfo = clusterMap[clusterName]!!
187+
val commandNameList = constructHint(selectedClusterInfo.commands)
188+
return ArrayAdapter(requireContext(), android.R.layout.simple_list_item_1, commandNameList)
189+
}
190+
191+
private fun constructHint(clusterMap: Map<String, *>): Array<String> {
192+
return clusterMap.keys.toTypedArray()
193+
}
194+
195+
override fun onStop() {
196+
super.onStop()
197+
scope.cancel()
198+
}
199+
44200
companion object {
45201
private const val TAG = "ClusterDetailFragment"
46-
fun newInstance(): ClusterDetailFragment = ClusterDetailFragment()
202+
private const val ENDPOINT_ID_KEY = "endpoint_id"
203+
private const val DEVICE_PTR_KEY = "device_ptr"
204+
205+
fun newInstance(
206+
deviceId: Long,
207+
endpointId: Int
208+
): ClusterDetailFragment {
209+
return ClusterDetailFragment().apply {
210+
arguments = Bundle(2).apply {
211+
putLong(DEVICE_PTR_KEY, deviceId)
212+
putInt(ENDPOINT_ID_KEY, endpointId)
213+
}
214+
}
215+
}
47216
}
48217
}

src/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/clusterclient/clusterinteraction/ClusterInteractionFragment.kt

+13-26
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
package com.google.chip.chiptool.clusterclient.clusterinteraction
22

33
import android.os.Bundle
4-
import android.util.Log
54
import android.view.LayoutInflater
65
import android.view.View
76
import android.view.ViewGroup
87
import android.widget.Toast
98
import androidx.fragment.app.Fragment
109
import androidx.lifecycle.lifecycleScope
1110
import androidx.recyclerview.widget.LinearLayoutManager
12-
import chip.clusterinfo.ClusterInfo
1311
import chip.devicecontroller.ChipDeviceController
14-
import chip.devicecontroller.ClusterInfoMapping
1512
import com.google.chip.chiptool.ChipClient
1613
import com.google.chip.chiptool.GenericChipDeviceListener
1714
import com.google.chip.chiptool.R
15+
import kotlinx.coroutines.CoroutineScope
16+
import kotlinx.coroutines.Dispatchers
17+
import kotlinx.coroutines.Job
18+
import kotlinx.coroutines.cancel
1819
import com.google.chip.chiptool.clusterclient.AddressUpdateFragment
1920
import kotlinx.android.synthetic.main.cluster_interaction_fragment.view.endpointList
2021
import kotlinx.android.synthetic.main.cluster_interaction_fragment.view.getEndpointListBtn
21-
import kotlinx.coroutines.CoroutineScope
2222
import kotlinx.coroutines.launch
2323

2424
class ClusterInteractionFragment : Fragment() {
@@ -27,7 +27,7 @@ class ClusterInteractionFragment : Fragment() {
2727

2828
private lateinit var scope: CoroutineScope
2929
private lateinit var addressUpdateFragment: AddressUpdateFragment
30-
private lateinit var clusterMap: Map<String, ClusterInfo>
30+
private var devicePtr = 0L
3131

3232
override fun onCreateView(
3333
inflater: LayoutInflater,
@@ -37,17 +37,18 @@ class ClusterInteractionFragment : Fragment() {
3737
scope = viewLifecycleOwner.lifecycleScope
3838

3939
return inflater.inflate(R.layout.cluster_interaction_fragment, container, false).apply {
40-
deviceController.setCompletionListener(ChipControllerCallback())
40+
deviceController.setCompletionListener(GenericChipDeviceListener())
41+
endpointList.visibility = View.GONE
4142
getEndpointListBtn.setOnClickListener {
4243
scope.launch {
44+
devicePtr =
45+
ChipClient.getConnectedDevicePointer(requireContext(), addressUpdateFragment.deviceId)
4346
showMessage("Retrieving endpoints")
4447
endpointList.visibility = View.VISIBLE
4548
}
4649
}
47-
4850
addressUpdateFragment =
4951
childFragmentManager.findFragmentById(R.id.addressUpdateFragment) as AddressUpdateFragment
50-
clusterMap = ClusterInfoMapping().clusterMap
5152
var dataList: List<EndpointItem> = ArrayList()
5253
// TODO: Dynamically retrieve endpoint information using descriptor cluster
5354
// hardcode the endpoint for now
@@ -65,23 +66,9 @@ class ClusterInteractionFragment : Fragment() {
6566
}
6667
}
6768

68-
inner class ChipControllerCallback : GenericChipDeviceListener() {
69-
override fun onConnectDeviceComplete() {}
70-
71-
override fun onCommissioningComplete(nodeId: Long, errorCode: Int) {
72-
}
73-
74-
override fun onNotifyChipConnectionClosed() {
75-
Log.d(TAG, "onNotifyChipConnectionClosed")
76-
}
77-
78-
override fun onCloseBleComplete() {
79-
Log.d(TAG, "onCloseBleComplete")
80-
}
81-
82-
override fun onError(error: Throwable?) {
83-
Log.d(TAG, "onError: $error")
84-
}
69+
override fun onStop() {
70+
super.onStop()
71+
scope.cancel()
8572
}
8673

8774
companion object {
@@ -104,7 +91,7 @@ class ClusterInteractionFragment : Fragment() {
10491
inner class EndpointListener : EndpointAdapter.OnItemClickListener {
10592
override fun onItemClick(position: Int) {
10693
Toast.makeText(requireContext(), "Item $position clicked", Toast.LENGTH_SHORT).show()
107-
showFragment(ClusterDetailFragment.newInstance())
94+
showFragment(ClusterDetailFragment.newInstance(devicePtr, position))
10895
}
10996
}
11097
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
2+
xmlns:app="http://schemas.android.com/apk/res-auto"
3+
android:id="@+id/clusterCallbackRow"
4+
android:layout_width="match_parent"
5+
android:layout_height="wrap_content">
6+
7+
<TextView
8+
android:id="@+id/clusterCallbackNameTv"
9+
android:layout_width="80dp"
10+
android:layout_height="wrap_content"
11+
android:layout_marginHorizontal="10dp"
12+
android:padding="16dp"
13+
android:minWidth="48dp"
14+
app:layout_constraintBottom_toBottomOf="parent"
15+
app:layout_constraintEnd_toStartOf="@id/clusterCallbackDataTv"
16+
app:layout_constraintStart_toStartOf="parent"
17+
app:layout_constraintTop_toTopOf="parent" />
18+
19+
<TextView
20+
android:id="@+id/clusterCallbackDataTv"
21+
android:layout_width="0dp"
22+
android:layout_height="wrap_content"
23+
android:padding="16dp"
24+
android:singleLine="false"
25+
app:layout_constraintBottom_toBottomOf="parent"
26+
app:layout_constraintEnd_toStartOf="@id/clusterCallbackTypeTv"
27+
app:layout_constraintStart_toEndOf="@id/clusterCallbackNameTv"
28+
app:layout_constraintTop_toTopOf="parent" />
29+
30+
31+
<TextView
32+
android:id="@+id/clusterCallbackTypeTv"
33+
android:textStyle="bold"
34+
android:layout_width="100dp"
35+
android:layout_height="wrap_content"
36+
android:padding="16dp"
37+
android:singleLine="false"
38+
app:layout_constraintBottom_toBottomOf="parent"
39+
app:layout_constraintEnd_toEndOf="parent"
40+
app:layout_constraintStart_toEndOf="@id/clusterCallbackDataTv"
41+
app:layout_constraintTop_toTopOf="parent" />
42+
</androidx.constraintlayout.widget.ConstraintLayout>

0 commit comments

Comments
 (0)