Skip to content

Commit

Permalink
Modify wildcardFragment for multiple attribute,event (#25677)
Browse files Browse the repository at this point in the history
  • Loading branch information
joonhaengHeo authored and pull[bot] committed Jan 19, 2024
1 parent 80f411d commit 1126600
Show file tree
Hide file tree
Showing 6 changed files with 418 additions and 175 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import android.widget.Button
import android.widget.EditText
import android.widget.Spinner
import android.widget.TextView
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import chip.devicecontroller.ChipDeviceController
Expand All @@ -28,6 +29,7 @@ import chip.devicecontroller.model.InvokeElement
import chip.devicecontroller.model.NodeState
import chip.tlv.AnonymousTag
import chip.tlv.ContextSpecificTag
import chip.tlv.TlvReader
import chip.tlv.TlvWriter
import com.google.chip.chiptool.ChipClient
import com.google.chip.chiptool.R
Expand All @@ -37,6 +39,8 @@ import java.lang.StringBuilder
import java.util.Optional
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

class WildcardFragment : Fragment() {
private var _binding: WildcardFragmentBinding? = null
Expand All @@ -49,6 +53,10 @@ class WildcardFragment : Fragment() {

private lateinit var addressUpdateFragment: AddressUpdateFragment

private val attributePath = ArrayList<ChipAttributePath>()
private val eventPath = ArrayList<ChipEventPath>()
private val subscribeIdList = ArrayList<ULong>()

private val reportCallback = object : ReportCallback {
override fun onError(attributePath: ChipAttributePath?, eventPath: ChipEventPath?, ex: Exception) {
if (attributePath != null)
Expand Down Expand Up @@ -108,19 +116,86 @@ class WildcardFragment : Fragment() {
): View {
_binding = WildcardFragmentBinding.inflate(inflater, container, false)
scope = viewLifecycleOwner.lifecycleScope
binding.subscribeBtn.setOnClickListener { scope.launch { showSubscribeDialog(ATTRIBUTE) } }
binding.readBtn.setOnClickListener { scope.launch { showReadDialog(ATTRIBUTE) } }
binding.writeBtn.setOnClickListener { scope.launch { showWriteDialog() } }
binding.subscribeEventBtn.setOnClickListener { scope.launch { showSubscribeDialog(EVENT) } }
binding.readEventBtn.setOnClickListener { scope.launch { showReadDialog(EVENT) } }
binding.invokeBtn.setOnClickListener { scope.launch { showInvokeDialog() } }

binding.selectTypeRadioGroup.setOnCheckedChangeListener { _, i ->
val readBtnOn = (i == R.id.readRadioBtn)
val subscribeBtnOn = (i == R.id.subscribeRadioBtn)
val writeBtnOn = (i == R.id.writeRadioBtn)
val invokeBtnOn = (i == R.id.invokeRadioBtn)

binding.addLayout.visibility = getVisibility(readBtnOn || subscribeBtnOn)
binding.attributeIdLabel.visibility = getVisibility(readBtnOn || subscribeBtnOn || writeBtnOn)
binding.attributeIdEd.visibility = getVisibility(readBtnOn || subscribeBtnOn || writeBtnOn)
binding.eventIdLabel.visibility = getVisibility(readBtnOn || subscribeBtnOn)
binding.eventIdEd.visibility = getVisibility(readBtnOn || subscribeBtnOn)
binding.commandIdLabel.visibility = getVisibility(invokeBtnOn)
binding.commandIdEd.visibility = getVisibility(invokeBtnOn)
binding.isUrgentLabel.visibility = getVisibility(subscribeBtnOn)
binding.isUrgentSp.visibility = getVisibility(subscribeBtnOn)
binding.shutdownSubscriptionBtn.visibility = getVisibility(subscribeBtnOn)
}

binding.sendBtn.setOnClickListener {
if (binding.readRadioBtn.isChecked) {
showReadDialog()
} else if (binding.subscribeRadioBtn.isChecked) {
showSubscribeDialog()
} else if (binding.writeRadioBtn.isChecked) {
showWriteDialog()
} else if (binding.invokeRadioBtn.isChecked) {
showInvokeDialog()
}
}

binding.shutdownSubscriptionBtn.setOnClickListener { showShutdownSubscriptionDialog() }

binding.addAttributeBtn.setOnClickListener { addPathList(ATTRIBUTE) }
binding.addEventBtn.setOnClickListener { addPathList(EVENT) }
binding.resetBtn.setOnClickListener { resetPath() }

addressUpdateFragment =
childFragmentManager.findFragmentById(R.id.addressUpdateFragment) as AddressUpdateFragment

return binding.root
}

private fun getVisibility(isShowing: Boolean) : Int {
return if (isShowing) { View.VISIBLE } else { View.GONE }
}

private fun addPathList(type: Int) {
val endpointId = getChipPathIdForText(binding.endpointIdEd.text.toString())
val clusterId = getChipPathIdForText(binding.clusterIdEd.text.toString())
val attributeId = getChipPathIdForText(binding.attributeIdEd.text.toString())
val eventId = getChipPathIdForText(binding.eventIdEd.text.toString())
// Only Subscribe used
val isUrgent = (binding.subscribeRadioBtn.isChecked) && (binding.isUrgentSp.selectedItem.toString().toBoolean())

if (type == ATTRIBUTE) {
attributePath.add(ChipAttributePath.newInstance(endpointId, clusterId, attributeId))
} else if (type == EVENT) {
eventPath.add(ChipEventPath.newInstance(endpointId, clusterId, eventId, isUrgent))
}
updateAddListView()
}

private fun resetPath() {
attributePath.clear()
eventPath.clear()
updateAddListView()
}

private fun updateAddListView() {
val builder = StringBuilder()
for (attribute in attributePath) {
builder.append("attribute($attribute)\n")
}
for (event in eventPath) {
builder.append("event($event)\n")
}
binding.sendListView.text = builder.toString()
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
Expand Down Expand Up @@ -154,75 +229,43 @@ class WildcardFragment : Fragment() {
return stringBuilder.toString()
}

private suspend fun subscribe(type: Int, minInterval: Int, maxInterval: Int, keepSubscriptions: Boolean, isFabricFiltered: Boolean, isUrgent: Boolean) {
private suspend fun subscribe(minInterval: Int, maxInterval: Int, keepSubscriptions: Boolean, isFabricFiltered: Boolean) {
val subscriptionEstablishedCallback =
SubscriptionEstablishedCallback { Log.i(TAG, "Subscription to device established") }
SubscriptionEstablishedCallback {
subscriptionId ->
Log.i(TAG, "Subscription to device established : ${subscriptionId.toULong()}")
subscribeIdList.add(subscriptionId.toULong())
requireActivity().runOnUiThread {
Toast.makeText(requireActivity(), "${getString(R.string.wildcard_subscribe_established_toast_message)} : $subscriptionId", Toast.LENGTH_SHORT).show()
}
}

val resubscriptionAttemptCallback =
ResubscriptionAttemptCallback { terminationCause, nextResubscribeIntervalMsec
-> Log.i(TAG, "ResubscriptionAttempt terminationCause:$terminationCause, nextResubscribeIntervalMsec:$nextResubscribeIntervalMsec") }

val endpointId = getChipPathIdForText(binding.endpointIdEd.text.toString())
val clusterId = getChipPathIdForText(binding.clusterIdEd.text.toString())
val attributeId = getChipPathIdForText(binding.attributeIdEd.text.toString())
val eventId = getChipPathIdForText(binding.eventIdEd.text.toString())

if (type == ATTRIBUTE) {
val attributePath = ChipAttributePath.newInstance(endpointId, clusterId, attributeId)
deviceController.subscribeToPath(subscriptionEstablishedCallback,
resubscriptionAttemptCallback,
reportCallback,
ChipClient.getConnectedDevicePointer(requireContext(),
addressUpdateFragment.deviceId),
listOf(attributePath),
null,
minInterval,
maxInterval,
keepSubscriptions,
isFabricFiltered,
/* imTimeoutMs= */ 0)
} else if (type == EVENT) {
val eventPath = ChipEventPath.newInstance(endpointId, clusterId, eventId, isUrgent)
deviceController.subscribeToPath(subscriptionEstablishedCallback,
resubscriptionAttemptCallback,
reportCallback,
ChipClient.getConnectedDevicePointer(requireContext(),
addressUpdateFragment.deviceId),
null,
listOf(eventPath),
minInterval,
maxInterval,
keepSubscriptions,
isFabricFiltered,
/* imTimeoutMs= */ 0)
}
deviceController.subscribeToPath(subscriptionEstablishedCallback,
resubscriptionAttemptCallback,
reportCallback,
ChipClient.getConnectedDevicePointer(requireContext(),
addressUpdateFragment.deviceId),
attributePath.ifEmpty { null },
eventPath.ifEmpty { null },
minInterval,
maxInterval,
keepSubscriptions,
isFabricFiltered,
/* imTimeoutMs= */ 0)
}

private suspend fun read(type: Int, isFabricFiltered: Boolean) {
val endpointId = getChipPathIdForText(binding.endpointIdEd.text.toString())
val clusterId = getChipPathIdForText(binding.clusterIdEd.text.toString())
val attributeId = getChipPathIdForText(binding.attributeIdEd.text.toString())
val eventId = getChipPathIdForText(binding.eventIdEd.text.toString())

if (type == ATTRIBUTE) {
val attributePath = ChipAttributePath.newInstance(endpointId, clusterId, attributeId)
deviceController.readPath(reportCallback,
ChipClient.getConnectedDevicePointer(requireContext(),
addressUpdateFragment.deviceId),
listOf(attributePath),
null,
isFabricFiltered,
/* imTimeoutMs= */ 0)
} else if (type == EVENT) {
val eventPath = ChipEventPath.newInstance(endpointId, clusterId, eventId)
deviceController.readPath(reportCallback,
ChipClient.getConnectedDevicePointer(requireContext(),
addressUpdateFragment.deviceId),
null,
listOf(eventPath),
isFabricFiltered,
/* imTimeoutMs= */ 0)
}
private suspend fun read(isFabricFiltered: Boolean) {
deviceController.readPath(reportCallback,
ChipClient.getConnectedDevicePointer(requireContext(),
addressUpdateFragment.deviceId),
attributePath.ifEmpty { null },
eventPath.ifEmpty { null },
isFabricFiltered,
/* imTimeoutMs= */ 0)
}

private suspend fun write(writeValueType: String, writeValue: String, dataVersion: Int?, timedRequestTimeoutMs: Int, imTimeoutMs: Int) {
Expand Down Expand Up @@ -285,7 +328,13 @@ class WildcardFragment : Fragment() {
imTimeoutMs)
}

private fun showReadDialog(type: Int) {
private fun showReadDialog() {
if (attributePath.isEmpty() && eventPath.isEmpty()) {
requireActivity().runOnUiThread {
Toast.makeText(requireActivity(), R.string.wildcard_empty_error_toast_message, Toast.LENGTH_SHORT).show()
}
return
}
val dialogView = requireActivity().layoutInflater.inflate(R.layout.read_dialog, null)
val dialog = AlertDialog.Builder(requireContext()).apply {
setView(dialogView)
Expand All @@ -294,7 +343,7 @@ class WildcardFragment : Fragment() {
val isFabricFilteredEd = dialogView.findViewById<EditText>(R.id.isFabricFilteredSp)
dialogView.findViewById<Button>(R.id.readBtn).setOnClickListener {
scope.launch {
read(type, isFabricFilteredEd.text.toString().toBoolean())
read(isFabricFilteredEd.text.toString().toBoolean())
requireActivity().runOnUiThread { dialog.dismiss() }
}
}
Expand All @@ -305,7 +354,7 @@ class WildcardFragment : Fragment() {
binding.outputTv.text = ""
val dialogView = requireActivity().layoutInflater.inflate(R.layout.write_dialog, null)
val writeValueTypeSp = dialogView.findViewById<Spinner>(R.id.writeValueTypeSp)
val spinnerAdapter = ArrayAdapter(requireActivity(), android.R.layout.simple_spinner_item, TLV_MAP.keys.toList())
val spinnerAdapter = ArrayAdapter(requireActivity(), android.R.layout.simple_spinner_dropdown_item, TLV_MAP.keys.toList())
writeValueTypeSp.adapter = spinnerAdapter
val dialog = AlertDialog.Builder(requireContext()).apply {
setView(dialogView)
Expand All @@ -328,17 +377,14 @@ class WildcardFragment : Fragment() {
dialog.show()
}

private fun showSubscribeDialog(type: Int) {
val dialogView = requireActivity().layoutInflater.inflate(R.layout.subscribe_dialog, null)
val isUrgentTv = dialogView.findViewById<TextView>(R.id.titleisUrgent)
val isUrgentSp = dialogView.findViewById<Spinner>(R.id.isUrgentSp)
if (type == EVENT) {
isUrgentTv.visibility = View.VISIBLE
isUrgentSp.visibility = View.VISIBLE
} else {
isUrgentTv.visibility = View.GONE
isUrgentSp.visibility = View.GONE
private fun showSubscribeDialog() {
if (attributePath.isEmpty() && eventPath.isEmpty()) {
requireActivity().runOnUiThread {
Toast.makeText(requireActivity(), R.string.wildcard_empty_error_toast_message, Toast.LENGTH_SHORT).show()
}
return
}
val dialogView = requireActivity().layoutInflater.inflate(R.layout.subscribe_dialog, null)
val dialog = AlertDialog.Builder(requireContext()).apply {
setView(dialogView)
}.create()
Expand All @@ -351,12 +397,10 @@ class WildcardFragment : Fragment() {
scope.launch {
if(minIntervalEd.text.isNotBlank() && maxIntervalEd.text.isNotBlank()) {
subscribe(
type,
minIntervalEd.text.toString().toInt(),
maxIntervalEd.text.toString().toInt(),
keepSubscriptionsSp.selectedItem.toString().toBoolean(),
isFabricFilteredSp.selectedItem.toString().toBoolean(),
isUrgentSp.selectedItem.toString().toBoolean(),
)
} else {
Log.e(TAG, "minInterval or maxInterval is empty!" )
Expand Down Expand Up @@ -389,6 +433,87 @@ class WildcardFragment : Fragment() {
dialog.show()
}

private suspend fun readCurrentFabricIndex() : UInt {
val context = requireContext()
val endpointId = 0L
val clusterId = 62L // OperationalCredentials
val attributeId = 5L // CurrentFabricIndex
val deviceId = addressUpdateFragment.deviceId
val devicePointer = ChipClient.getConnectedDevicePointer(context, deviceId)
return suspendCoroutine { cont ->
deviceController.readAttributePath(object : ReportCallback {
override fun onError(attributePath: ChipAttributePath?, eventPath: ChipEventPath?, e: java.lang.Exception?) {
cont.resume(0u)
}

override fun onReport(nodeState: NodeState?) {
val state = nodeState?.getEndpointState(endpointId.toInt())?.
getClusterState(clusterId)?.
getAttributeState(attributeId)
if (state == null) {
cont.resume(0u)
return
}
val ret = TlvReader(state.tlv).getUInt(AnonymousTag)
cont.resume(ret)
}
}, devicePointer,
listOf(ChipAttributePath.newInstance(endpointId, clusterId, attributeId)),
0 /* imTimeoutMs */
)
}
}

private fun shutdownSubscription(fabricIndex: UInt, subscribeId: ULong? = null) {
val deviceId = addressUpdateFragment.deviceId
if (subscribeId != null) {
deviceController.shutdownSubscriptions(fabricIndex.toInt(), deviceId, subscribeId.toLong())
subscribeIdList.remove(subscribeId)
} else {
deviceController.shutdownSubscriptions(fabricIndex.toInt(), deviceId)
}
}

private fun showShutdownSubscriptionDialog() {
val dialogView = requireActivity().layoutInflater.inflate(R.layout.shutdown_subscribe_dialog, null)
val subscriptionIdSp = dialogView.findViewById<Spinner>(R.id.subscribeIdSp)
val fabricIndexTv = dialogView.findViewById<TextView>(R.id.fabricIndexValue)
val shutdownBtn = dialogView.findViewById<Button>(R.id.shutdownBtn)
val shutdownAllBtn = dialogView.findViewById<Button>(R.id.shutdownAllBtn)
val spinnerAdapter = ArrayAdapter(requireActivity(), android.R.layout.simple_spinner_dropdown_item, subscribeIdList)
subscriptionIdSp.adapter = spinnerAdapter
val dialog = AlertDialog.Builder(requireContext()).apply {
setView(dialogView)
}.create()

shutdownBtn.setOnClickListener {
val fabricIndex = fabricIndexTv.text.toString().toUInt()
val subscribeId = subscriptionIdSp.selectedItem.toString().toULong()
scope.launch {
shutdownSubscription(fabricIndex, subscribeId)
requireActivity().runOnUiThread { dialog.dismiss() }
}
}

shutdownAllBtn.setOnClickListener {
scope.launch {
val fabricIndex = fabricIndexTv.text.toString().toUInt()
shutdownSubscription(fabricIndex)
requireActivity().runOnUiThread { dialog.dismiss() }
}
}
dialog.show()

scope.launch {
val fabricIndex = readCurrentFabricIndex()
requireActivity().runOnUiThread {
fabricIndexTv.text = fabricIndex.toString()
shutdownBtn.isEnabled = true
shutdownAllBtn.isEnabled = true
}
}
}

private fun getChipPathIdForText(text: String): ChipPathId {
return if (text.isEmpty()) ChipPathId.forWildcard() else ChipPathId.forId(text.toLong())
}
Expand Down
Loading

0 comments on commit 1126600

Please sign in to comment.