blob: 4324ba535d886a2a75470983679fd29016ca74f9 [file] [log] [blame]
package com.google.chip.chiptool.clusterclient
import android.app.AlertDialog
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
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
import chip.devicecontroller.ChipIdLookup
import chip.devicecontroller.ExtendableInvokeCallback
import chip.devicecontroller.ReportCallback
import chip.devicecontroller.ResubscriptionAttemptCallback
import chip.devicecontroller.SubscriptionEstablishedCallback
import chip.devicecontroller.WriteAttributesCallback
import chip.devicecontroller.model.AttributeWriteRequest
import chip.devicecontroller.model.ChipAttributePath
import chip.devicecontroller.model.ChipEventPath
import chip.devicecontroller.model.ChipPathId
import chip.devicecontroller.model.InvokeElement
import chip.devicecontroller.model.InvokeResponseData
import chip.devicecontroller.model.NoInvokeResponseData
import chip.devicecontroller.model.NodeState
import chip.devicecontroller.model.Status
import com.google.chip.chiptool.ChipClient
import com.google.chip.chiptool.R
import com.google.chip.chiptool.databinding.WildcardFragmentBinding
import com.google.chip.chiptool.util.toAny
import java.lang.StringBuilder
import java.util.Optional
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import matter.jsontlv.putJsonString
import matter.tlv.AnonymousTag
import matter.tlv.TlvReader
import matter.tlv.TlvWriter
class WildcardFragment : Fragment(), AddressUpdateFragment.ICDCheckInMessageCallback {
private var _binding: WildcardFragmentBinding? = null
private val binding
get() = _binding!!
private val deviceController: ChipDeviceController
get() = ChipClient.getDeviceController(requireContext())
private lateinit var scope: CoroutineScope
private lateinit var addressUpdateFragment: AddressUpdateFragment
private val attributePath = ArrayList<ChipAttributePath>()
private val eventPath = ArrayList<ChipEventPath>()
private val writePath = ArrayList<AttributeWriteRequest>()
private val invokePath = ArrayList<InvokeElement>()
private val subscribeIdList = ArrayList<ULong>()
data class ReadICDConfig(val isFabricFiltered: Boolean, val eventMin: Long?)
data class SubscribeICDConfig(
val minInterval: Int,
val maxInterval: Int,
val keepSubscriptions: Boolean,
val isFabricFiltered: Boolean,
val eventMin: Long?
)
data class WriteInvokeICDConfig(val timedRequestTimeoutMs: Int, val imTimeoutMs: Int)
private var readICDConfig: ReadICDConfig? = null
private var subscribeICDConfig: SubscribeICDConfig? = null
private var writeICDConfig: WriteInvokeICDConfig? = null
private var invokeICDConfig: WriteInvokeICDConfig? = null
private val reportCallback =
object : ReportCallback {
override fun onError(
attributePath: ChipAttributePath?,
eventPath: ChipEventPath?,
ex: Exception
) {
if (attributePath != null) {
Log.e(TAG, "Report error for $attributePath: $ex")
}
if (eventPath != null) {
Log.e(TAG, "Report error for $eventPath: $ex")
}
}
override fun onReport(nodeState: NodeState) {
Log.i(TAG, "Received wildcard report")
val debugString = nodeStateToDebugString(nodeState)
Log.i(TAG, debugString)
requireActivity().runOnUiThread { binding.outputTv.text = debugString }
}
override fun onDone() {
Log.i(TAG, "wildcard report Done")
}
}
private val writeAttributeCallback =
object : WriteAttributesCallback {
var viewText = ""
override fun onError(attributePath: ChipAttributePath?, ex: Exception?) {
Log.e(TAG, "Report error for $attributePath: $ex")
viewText += "onError : $attributePath - $ex\n"
}
override fun onResponse(attributePath: ChipAttributePath, status: Status) {
viewText += "$attributePath : Write response: $status\n"
}
override fun onDone() {
requireActivity().runOnUiThread {
binding.outputTv.text = viewText
viewText = ""
}
}
}
private val extendableInvokeCallback =
object : ExtendableInvokeCallback {
var viewText = ""
override fun onError(e: java.lang.Exception?) {
viewText += "Invoke onError : $e\n"
Log.e(TAG, "Report error", e)
}
override fun onResponse(invokeResponseData: InvokeResponseData?) {
viewText += "Invoke Response : $invokeResponseData\n"
}
override fun onNoResponse(noInvokeResponseData: NoInvokeResponseData?) {
viewText += "Invoke onNoResponse : $noInvokeResponseData\n"
}
override fun onDone() {
requireActivity().runOnUiThread {
binding.outputTv.text = viewText
viewText = ""
}
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
_binding = WildcardFragmentBinding.inflate(inflater, container, false)
scope = viewLifecycleOwner.lifecycleScope
val writeTypeSpinnerAdapter =
ArrayAdapter(
requireActivity(),
android.R.layout.simple_spinner_dropdown_item,
TLV_MAP.keys.toList()
)
binding.writeValueTypeSp.adapter = writeTypeSpinnerAdapter
binding.selectTypeRadioGroup.setOnCheckedChangeListener { _, radioBtnId ->
setVisibilityEachView(radioBtnId)
}
binding.sendBtn.setOnClickListener { showDialog() }
binding.shutdownSubscriptionBtn.setOnClickListener { showShutdownSubscriptionDialog() }
binding.addAttributeBtn.setOnClickListener { addPathList(SendType.ATTRIBUTE) }
binding.addEventBtn.setOnClickListener { addPathList(SendType.EVENT) }
binding.addListBtn.setOnClickListener { addRequest() }
binding.resetBtn.setOnClickListener { resetPath() }
binding.writeInvokeresetBtn.setOnClickListener { resetPath() }
addressUpdateFragment =
childFragmentManager.findFragmentById(R.id.addressUpdateFragment) as AddressUpdateFragment
return binding.root
}
override fun onResume() {
super.onResume()
addressUpdateFragment.setNotifyCheckInMessageCallback(this)
}
override fun onPause() {
addressUpdateFragment.setNotifyCheckInMessageCallback(null)
super.onPause()
}
override fun notifyCheckInMessage() {
Log.d(TAG, "notifyCheckInMessage")
if (attributePath.isNotEmpty() || eventPath.isNotEmpty()) {
if (binding.readRadioBtn.isChecked && readICDConfig != null) {
scope.launch { read(readICDConfig!!.isFabricFiltered, readICDConfig!!.eventMin) }
} else if (binding.subscribeRadioBtn.isChecked && subscribeICDConfig != null) {
scope.launch {
subscribe(
subscribeICDConfig!!.minInterval,
subscribeICDConfig!!.maxInterval,
subscribeICDConfig!!.keepSubscriptions,
subscribeICDConfig!!.isFabricFiltered,
subscribeICDConfig!!.eventMin
)
}
}
} else if (
binding.writeRadioBtn.isChecked && writePath.isNotEmpty() && writeICDConfig != null
) {
scope.launch { write(writeICDConfig!!.timedRequestTimeoutMs, writeICDConfig!!.imTimeoutMs) }
} else if (
binding.invokeRadioBtn.isChecked && invokePath.isNotEmpty() && invokeICDConfig != null
) {
scope.launch {
invoke(invokeICDConfig!!.timedRequestTimeoutMs, invokeICDConfig!!.imTimeoutMs)
}
}
}
private fun setVisibilityEachView(radioBtnId: Int) {
val readBtnOn = (radioBtnId == R.id.readRadioBtn)
val subscribeBtnOn = (radioBtnId == R.id.subscribeRadioBtn)
val writeBtnOn = (radioBtnId == R.id.writeRadioBtn)
val invokeBtnOn = (radioBtnId == R.id.invokeRadioBtn)
binding.addAttributeBtn.visibility = getVisibility(readBtnOn || subscribeBtnOn)
binding.addEventBtn.visibility = getVisibility(readBtnOn || subscribeBtnOn)
binding.resetBtn.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.addListBtn.visibility = getVisibility(writeBtnOn || invokeBtnOn)
binding.commandIdLabel.visibility = getVisibility(invokeBtnOn)
binding.commandIdEd.visibility = getVisibility(invokeBtnOn)
binding.isUrgentLabel.visibility = getVisibility(subscribeBtnOn)
binding.isUrgentSp.visibility = getVisibility(subscribeBtnOn)
binding.writeValueLabel.visibility = getVisibility(writeBtnOn)
binding.writeValueEd.visibility = getVisibility(writeBtnOn)
binding.writeValueTypeLabel.visibility = getVisibility(writeBtnOn)
binding.writeValueTypeSp.visibility = getVisibility(writeBtnOn)
binding.dataVersionLabel.visibility = getVisibility(writeBtnOn)
binding.dataVersionEd.visibility = getVisibility(writeBtnOn)
binding.invokeValueLabel.visibility = getVisibility(invokeBtnOn)
binding.invokeValueEd.visibility = getVisibility(invokeBtnOn)
binding.shutdownSubscriptionBtn.visibility = getVisibility(subscribeBtnOn)
binding.writeInvokeresetBtn.visibility = getVisibility(writeBtnOn || invokeBtnOn)
resetPath()
}
private fun showDialog() {
if (binding.readRadioBtn.isChecked) {
showReadDialog()
} else if (binding.subscribeRadioBtn.isChecked) {
showSubscribeDialog()
} else if (binding.writeRadioBtn.isChecked) {
showWriteDialog()
} else if (binding.invokeRadioBtn.isChecked) {
showInvokeDialog()
}
}
private fun addRequest() {
if (binding.writeRadioBtn.isChecked) {
addWriteRequest()
} else {
addInvokeRequest()
}
}
private fun getVisibility(isShowing: Boolean): Int {
return if (isShowing) {
View.VISIBLE
} else {
View.GONE
}
}
private fun addPathList(type: SendType) {
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 == SendType.ATTRIBUTE) {
attributePath.add(ChipAttributePath.newInstance(endpointId, clusterId, attributeId))
} else if (type == SendType.EVENT) {
eventPath.add(ChipEventPath.newInstance(endpointId, clusterId, eventId, isUrgent))
}
updateAddListView()
}
private fun resetPath() {
attributePath.clear()
eventPath.clear()
writePath.clear()
invokePath.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")
}
for (write in writePath) {
builder.append("WritePath($write)\n")
}
for (invoke in invokePath) {
builder.append("InvokePath($invoke)\n")
}
binding.sendListView.text = builder.toString()
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
private fun nodeStateToDebugString(nodeState: NodeState): String {
val stringBuilder = StringBuilder()
nodeState.endpointStates.forEach { (endpointId, endpointState) ->
stringBuilder.append("Endpoint $endpointId: {\n")
endpointState.clusterStates.forEach { (clusterId, clusterState) ->
stringBuilder.append("\t${ChipIdLookup.clusterIdToName(clusterId)}Cluster: {\n")
clusterState.attributeStates.forEach { (attributeId, attributeState) ->
val attributeName = ChipIdLookup.attributeIdToName(clusterId, attributeId)
val tlv = attributeState.tlv
stringBuilder.append("\t\t$attributeName: ${TlvReader(tlv).toAny()}\n")
}
clusterState.eventStates.forEach { (eventId, events) ->
for (event in events) {
stringBuilder.append("\t\teventNumber: ${event.eventNumber}\n")
stringBuilder.append("\t\tpriorityLevel: ${event.priorityLevel}\n")
stringBuilder.append("\t\ttimestampType: ${event.timestampType}\n")
stringBuilder.append("\t\ttimestampValue: ${event.timestampValue}\n")
val eventName = ChipIdLookup.eventIdToName(clusterId, eventId)
val tlv = event.tlv
stringBuilder.append("\t\t$eventName: ${TlvReader(tlv).toAny()}\n")
}
}
stringBuilder.append("\t}\n")
}
stringBuilder.append("}\n")
}
return stringBuilder.toString()
}
private suspend fun subscribe(
minInterval: Int,
maxInterval: Int,
keepSubscriptions: Boolean,
isFabricFiltered: Boolean,
eventMin: Long?
) {
val subscriptionEstablishedCallback = 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 devicePtr =
try {
ChipClient.getConnectedDevicePointer(requireContext(), addressUpdateFragment.deviceId)
} catch (e: IllegalStateException) {
Log.d(TAG, "getConnectedDevicePointer exception", e)
return
}
deviceController.subscribeToPath(
subscriptionEstablishedCallback,
resubscriptionAttemptCallback,
reportCallback,
devicePtr,
attributePath.ifEmpty { null },
eventPath.ifEmpty { null },
minInterval,
maxInterval,
keepSubscriptions,
isFabricFiltered,
/* imTimeoutMs= */ 0,
eventMin
)
}
private suspend fun read(isFabricFiltered: Boolean, eventMin: Long?) {
val devicePtr =
try {
ChipClient.getConnectedDevicePointer(requireContext(), addressUpdateFragment.deviceId)
} catch (e: IllegalStateException) {
Log.d(TAG, "getConnectedDevicePointer exception", e)
return
}
deviceController.readPath(
reportCallback,
devicePtr,
attributePath.ifEmpty { null },
eventPath.ifEmpty { null },
isFabricFiltered,
/* imTimeoutMs= */ 0,
eventMin
)
}
private fun addWriteRequest() {
val writeValue = binding.writeValueEd.text.toString()
val writeValueType = binding.writeValueTypeSp.selectedItem.toString()
val dataVersion = binding.dataVersionEd.text.toString()
val endpointId =
if (!addressUpdateFragment.isGroupId()) {
getChipPathIdForText(binding.endpointIdEd.text.toString())
} else {
null
}
val clusterId = getChipPathIdForText(binding.clusterIdEd.text.toString())
val attributeId = getChipPathIdForText(binding.attributeIdEd.text.toString())
val version =
if (dataVersion.isEmpty()) {
Optional.empty()
} else {
Optional.of(dataVersion.toInt())
}
lateinit var writeRequest: AttributeWriteRequest
if (writeValueType == "json") {
writeRequest =
AttributeWriteRequest.newInstance(endpointId, clusterId, attributeId, writeValue, version)
} else {
val tlvWriter = TlvWriter()
val values = writeValue.split(",")
if (values.size > 1) tlvWriter.startArray(AnonymousTag)
for (value in values) {
try {
TLV_MAP[writeValueType]?.generate(tlvWriter, value.trim())
} catch (ex: Exception) {
Log.e(TAG, "Invalid Data Type", ex)
return
}
}
if (values.size > 1) tlvWriter.endArray()
writeRequest =
AttributeWriteRequest.newInstance(
endpointId,
clusterId,
attributeId,
tlvWriter.getEncoded(),
version
)
}
writePath.add(writeRequest)
updateAddListView()
}
private fun addInvokeRequest() {
val endpointId = getChipPathIdForText(binding.endpointIdEd.text.toString())
val clusterId = getChipPathIdForText(binding.clusterIdEd.text.toString())
val commandId = getChipPathIdForText(binding.commandIdEd.text.toString())
val invokeJson = binding.invokeValueEd.text.toString()
val jsonString = invokeJson.ifEmpty { "{}" }
val invokeElement =
if (addressUpdateFragment.isGroupId()) {
InvokeElement.newGroupInstance(
addressUpdateFragment.getGroupId().toInt(),
clusterId,
commandId,
null,
jsonString
)
} else {
InvokeElement.newInstance(endpointId, clusterId, commandId, null, jsonString)
}
invokePath.add(invokeElement)
updateAddListView()
}
private suspend fun write(timedRequestTimeoutMs: Int, imTimeoutMs: Int) {
val devicePtr =
try {
addressUpdateFragment.getDevicePointer(requireContext())
} catch (e: IllegalStateException) {
Log.d(TAG, "getDevicePointer exception", e)
return
}
deviceController.write(
writeAttributeCallback,
devicePtr,
writePath,
timedRequestTimeoutMs,
imTimeoutMs
)
}
private suspend fun invoke(timedRequestTimeoutMs: Int, imTimeoutMs: Int) {
val devicePtr =
try {
addressUpdateFragment.getDevicePointer(requireContext())
} catch (e: IllegalStateException) {
Log.d(TAG, "getDevicePointer exception", e)
return
}
deviceController.extendableInvoke(
extendableInvokeCallback,
devicePtr,
invokePath,
timedRequestTimeoutMs,
imTimeoutMs
)
}
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 eventMinEd = dialogView.findViewById<EditText>(R.id.eventMinEd)
eventMinEd.visibility =
if (eventPath.isNotEmpty()) {
View.VISIBLE
} else {
View.GONE
}
val dialog = AlertDialog.Builder(requireContext()).apply { setView(dialogView) }.create()
val isFabricFilteredEd = dialogView.findViewById<Spinner>(R.id.isFabricFilteredSp)
dialogView.findViewById<Button>(R.id.readBtn).setOnClickListener {
scope.launch {
var eventMin: Long? = null
if (eventPath.isNotEmpty() && eventMinEd.text.isNotBlank()) {
eventMin = eventMinEd.text.toString().toULong().toLong()
}
if (addressUpdateFragment.isICDDevice()) {
readICDConfig =
ReadICDConfig(isFabricFilteredEd.selectedItem.toString().toBoolean(), eventMin)
} else {
read(isFabricFilteredEd.selectedItem.toString().toBoolean(), eventMin)
}
requireActivity().runOnUiThread { dialog.dismiss() }
}
}
dialog.show()
}
private fun showWriteDialog() {
binding.outputTv.text = ""
val dialogView = requireActivity().layoutInflater.inflate(R.layout.write_dialog, null)
val dialog = AlertDialog.Builder(requireContext()).apply { setView(dialogView) }.create()
dialogView.findViewById<Button>(R.id.writeBtn).setOnClickListener {
val timedRequestTimeoutMs =
dialogView.findViewById<EditText>(R.id.timedRequestTimeoutEd).text.toString()
val imTimeout = dialogView.findViewById<EditText>(R.id.imTimeoutEd).text.toString()
scope.launch {
val timedRequestTimeoutInt =
if (timedRequestTimeoutMs.isEmpty()) {
0
} else {
timedRequestTimeoutMs.toInt()
}
val imTimeoutInt =
if (imTimeout.isEmpty()) {
0
} else {
imTimeout.toInt()
}
if (addressUpdateFragment.isICDDevice()) {
writeICDConfig = WriteInvokeICDConfig(timedRequestTimeoutInt, imTimeoutInt)
} else {
write(timedRequestTimeoutInt, imTimeoutInt)
}
requireActivity().runOnUiThread { dialog.dismiss() }
}
}
dialog.show()
}
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 eventMinEd = dialogView.findViewById<EditText>(R.id.eventMinEd)
eventMinEd.visibility =
if (eventPath.isNotEmpty()) {
View.VISIBLE
} else {
View.GONE
}
val dialog = AlertDialog.Builder(requireContext()).apply { setView(dialogView) }.create()
val minIntervalEd = dialogView.findViewById<EditText>(R.id.minIntervalEd)
val maxIntervalEd = dialogView.findViewById<EditText>(R.id.maxIntervalEd)
val keepSubscriptionsSp = dialogView.findViewById<Spinner>(R.id.keepSubscriptionsSp)
val isFabricFilteredSp = dialogView.findViewById<Spinner>(R.id.isFabricFilteredSp)
dialogView.findViewById<Button>(R.id.subscribeBtn).setOnClickListener {
scope.launch {
if (minIntervalEd.text.isNotBlank() && maxIntervalEd.text.isNotBlank()) {
var eventMin: Long? = null
if (eventPath.isNotEmpty() && eventMinEd.text.isNotBlank()) {
eventMin = eventMinEd.text.toString().toULong().toLong()
}
if (addressUpdateFragment.isICDDevice()) {
subscribeICDConfig =
SubscribeICDConfig(
minIntervalEd.text.toString().toInt(),
maxIntervalEd.text.toString().toInt(),
keepSubscriptionsSp.selectedItem.toString().toBoolean(),
isFabricFilteredSp.selectedItem.toString().toBoolean(),
eventMin
)
} else {
subscribe(
minIntervalEd.text.toString().toInt(),
maxIntervalEd.text.toString().toInt(),
keepSubscriptionsSp.selectedItem.toString().toBoolean(),
isFabricFilteredSp.selectedItem.toString().toBoolean(),
eventMin
)
}
} else {
Log.e(TAG, "minInterval or maxInterval is empty!")
}
requireActivity().runOnUiThread { dialog.dismiss() }
}
}
dialog.show()
}
private fun showInvokeDialog() {
binding.outputTv.text = ""
val dialogView = requireActivity().layoutInflater.inflate(R.layout.invoke_dialog, null)
val dialog = AlertDialog.Builder(requireContext()).apply { setView(dialogView) }.create()
dialogView.findViewById<Button>(R.id.invokeBtn).setOnClickListener {
val timedRequestTimeoutMs =
dialogView.findViewById<EditText>(R.id.timedRequestTimeoutEd).text.toString()
val imTimeout = dialogView.findViewById<EditText>(R.id.imTimeoutEd).text.toString()
scope.launch {
val timedRequestTimeoutInt =
if (timedRequestTimeoutMs.isEmpty()) {
0
} else {
timedRequestTimeoutMs.toInt()
}
val imTimeoutInt =
if (imTimeout.isEmpty()) {
0
} else {
imTimeout.toInt()
}
if (addressUpdateFragment.isICDDevice()) {
invokeICDConfig = WriteInvokeICDConfig(timedRequestTimeoutInt, imTimeoutInt)
} else {
invoke(timedRequestTimeoutInt, imTimeoutInt)
}
requireActivity().runOnUiThread { dialog.dismiss() }
}
}
dialog.show()
}
private suspend fun readCurrentFabricIndex(): UInt {
val context = requireContext()
val endpointId = 0
val clusterId = 62L // OperationalCredentials
val attributeId = 5L // CurrentFabricIndex
val deviceId = addressUpdateFragment.deviceId
val devicePointer =
try {
ChipClient.getConnectedDevicePointer(context, deviceId)
} catch (e: IllegalStateException) {
Log.d(TAG, "getConnectedDevicePointer exception", e)
return 0U
}
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())
}
interface TlvWriterInterface {
fun generate(writer: TlvWriter, value: String, tag: matter.tlv.Tag = AnonymousTag)
}
companion object {
private const val TAG = "WildcardFragment"
private enum class SendType {
ATTRIBUTE,
EVENT
}
fun newInstance(): WildcardFragment = WildcardFragment()
private val TLV_MAP =
mapOf(
"json" to
object : TlvWriterInterface {
override fun generate(writer: TlvWriter, value: String, tag: matter.tlv.Tag) {
writer.putJsonString(tag, value)
}
},
"UnsignedInt" to
object : TlvWriterInterface {
override fun generate(writer: TlvWriter, value: String, tag: matter.tlv.Tag) {
writer.put(tag, value.toULong())
}
},
"Int" to
object : TlvWriterInterface {
override fun generate(writer: TlvWriter, value: String, tag: matter.tlv.Tag) {
writer.put(tag, value.toLong())
}
},
"Boolean" to
object : TlvWriterInterface {
override fun generate(writer: TlvWriter, value: String, tag: matter.tlv.Tag) {
writer.put(tag, value.toBoolean())
}
},
"Float" to
object : TlvWriterInterface {
override fun generate(writer: TlvWriter, value: String, tag: matter.tlv.Tag) {
writer.put(tag, value.toFloat())
}
},
"Double" to
object : TlvWriterInterface {
override fun generate(writer: TlvWriter, value: String, tag: matter.tlv.Tag) {
writer.put(tag, value.toDouble())
}
},
"String" to
object : TlvWriterInterface {
override fun generate(writer: TlvWriter, value: String, tag: matter.tlv.Tag) {
writer.put(tag, value)
}
},
"ByteArray(Hex)" to
object : TlvWriterInterface {
override fun generate(writer: TlvWriter, value: String, tag: matter.tlv.Tag) {
writer.put(
tag,
value.chunked(2).map { it.toInt(16) and 0xFF }.map { it.toByte() }.toByteArray()
)
}
},
)
}
}