Skip to content

Commit

Permalink
[Android] Implement Write Attribute in CHIPTool (#25457)
Browse files Browse the repository at this point in the history
* Implement Write API UI

* Update Wildcard Fragment for write

* Update examples/android/CHIPTool/app/src/main/res/values/strings.xml

Co-authored-by: Cliff Chung <116232729+cliffamzn@users.noreply.github.com>

* Modify some logs

---------

Co-authored-by: Cliff Chung <116232729+cliffamzn@users.noreply.github.com>
  • Loading branch information
2 people authored and pull[bot] committed Jul 10, 2023
1 parent a7dd0ce commit 9769516
Show file tree
Hide file tree
Showing 5 changed files with 249 additions and 1 deletion.
2 changes: 2 additions & 0 deletions examples/android/CHIPTool/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ dependencies {
implementation "androidx.camera:camera-camera2:${camerax_version}"
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
implementation "androidx.camera:camera-view:${camerax_version}"

implementation 'com.google.protobuf:protobuf-java:3.22.0'
}

repositories {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ 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
Expand All @@ -17,14 +18,20 @@ import chip.devicecontroller.ChipIdLookup
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.NodeState
import chip.tlv.AnonymousTag
import chip.tlv.TlvWriter
import com.google.chip.chiptool.ChipClient
import com.google.chip.chiptool.R
import com.google.chip.chiptool.databinding.WildcardFragmentBinding
import com.google.protobuf.ByteString
import java.lang.StringBuilder
import java.util.Optional
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

Expand Down Expand Up @@ -64,6 +71,21 @@ class WildcardFragment : Fragment() {
}
}

private val writeAttributeCallback = object : WriteAttributesCallback {
override fun onError(attributePath: ChipAttributePath?, ex: Exception?) {
Log.e(TAG, "Report error for $attributePath: $ex")
}

override fun onResponse(attributePath: ChipAttributePath?) {
val text = "$attributePath : Write Success"
requireActivity().runOnUiThread { binding.outputTv.text = text }
}

override fun onDone() {
Log.i(TAG, "write attribute Done")
}
}

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
Expand All @@ -73,6 +95,7 @@ class WildcardFragment : Fragment() {
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) } }

Expand Down Expand Up @@ -182,6 +205,35 @@ class WildcardFragment : Fragment() {
}
}

private suspend fun write(writeValueType: String, writeValue: String, dataVersion: Int?, timedRequestTimeoutMs: Int, imTimeoutMs: Int) {
val endpointId = getChipPathIdForText(binding.endpointIdEd.text.toString())
val clusterId = getChipPathIdForText(binding.clusterIdEd.text.toString())
val attributeId = getChipPathIdForText(binding.attributeIdEd.text.toString())
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()

val version = if (dataVersion == null) { Optional.empty() } else { Optional.of(dataVersion) }

val writeRequest = AttributeWriteRequest.newInstance(endpointId, clusterId, attributeId, tlvWriter.getEncoded(), version)
deviceController.write(writeAttributeCallback,
ChipClient.getConnectedDevicePointer(requireContext(),
addressUpdateFragment.deviceId),
listOf(writeRequest),
timedRequestTimeoutMs,
imTimeoutMs)
}

private fun showReadDialog(type: Int) {
val dialogView = requireActivity().layoutInflater.inflate(R.layout.read_dialog, null)
val dialog = AlertDialog.Builder(requireContext()).apply {
Expand All @@ -198,6 +250,33 @@ class WildcardFragment : Fragment() {
dialog.show()
}

private fun showWriteDialog() {
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())
writeValueTypeSp.adapter = spinnerAdapter
val dialog = AlertDialog.Builder(requireContext()).apply {
setView(dialogView)
}.create()

dialogView.findViewById<Button>(R.id.writeBtn).setOnClickListener {
val writeValue = dialogView.findViewById<EditText>(R.id.writeValueEd).text.toString()
val dataVersion = dialogView.findViewById<EditText>(R.id.dataVersionEd).text.toString()
val timedRequestTimeoutMs = dialogView.findViewById<EditText>(R.id.timedRequestTimeoutEd).text.toString()
val imTimeout = dialogView.findViewById<EditText>(R.id.imTimeoutEd).text.toString()
scope.launch {
write( writeValueTypeSp.selectedItem.toString(),
writeValue,
if (dataVersion.isEmpty()) { null } else { dataVersion.toInt() },
if (timedRequestTimeoutMs.isEmpty()) { 0 } else { timedRequestTimeoutMs.toInt() },
if (imTimeout.isEmpty()) { 0 } else { imTimeout.toInt() } )
requireActivity().runOnUiThread { dialog.dismiss() }
}
}
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)
Expand Down Expand Up @@ -241,10 +320,53 @@ class WildcardFragment : Fragment() {
return if (text.isEmpty()) ChipPathId.forWildcard() else ChipPathId.forId(text.toLong())
}

interface TlvWriterInterface {
fun generate(writer : TlvWriter, value: String)
}

companion object {
private const val TAG = "WildcardFragment"
private const val ATTRIBUTE = 1
private const val EVENT = 2
fun newInstance(): WildcardFragment = WildcardFragment()

private val TLV_MAP = mapOf(
"UnsignedInt" to object:TlvWriterInterface {
override fun generate(writer : TlvWriter, value: String) {
writer.put(AnonymousTag, value.toUInt())
}
},
"Int" to object:TlvWriterInterface {
override fun generate(writer : TlvWriter, value: String) {
writer.put(AnonymousTag, value.toInt())
}
},
"Boolean" to object:TlvWriterInterface {
override fun generate(writer : TlvWriter, value: String) {
writer.put(AnonymousTag, value.toBoolean())
}
},
"Float" to object:TlvWriterInterface {
override fun generate(writer : TlvWriter, value: String) {
writer.put(AnonymousTag, value.toFloat())
}
},
"Double" to object:TlvWriterInterface {
override fun generate(writer : TlvWriter, value: String) {
writer.put(AnonymousTag, value.toDouble())
}
},
"String" to object:TlvWriterInterface {
override fun generate(writer : TlvWriter, value: String) {
writer.put(AnonymousTag, value)
}
},
"ByteArray(Hex)" to object:TlvWriterInterface {
override fun generate(writer : TlvWriter, value: String) {
val byteStringValue = ByteString.fromHex(value)
writer.put(AnonymousTag, byteStringValue)
}
},
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,20 @@
android:text="@string/wildcard_read_btn_text"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/writeBtn"
app:layout_constraintTop_toBottomOf="@id/flow"/>

<Button
android:id="@+id/writeBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="8dp"
android:layout_gravity="center"
android:text="@string/wildcard_write_btn_text"
android:textSize="16sp"
app:layout_constraintStart_toEndOf="@id/readBtn"
app:layout_constraintEnd_toStartOf="@id/subscribeBtn"
app:layout_constraintTop_toBottomOf="@id/flow"/>

Expand All @@ -116,7 +130,7 @@
android:layout_gravity="center"
android:text="@string/wildcard_subscribe_btn_text"
android:textSize="16sp"
app:layout_constraintStart_toEndOf="@id/readBtn"
app:layout_constraintStart_toEndOf="@id/writeBtn"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/flow"/>

Expand Down
100 changes: 100 additions & 0 deletions examples/android/CHIPTool/app/src/main/res/layout/write_dialog.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="16dp">

<TextView
android:id="@+id/titleText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/write_dialog_title_text"
android:textSize="22sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<TextView
android:id="@+id/titleValueTypeTv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/write_dialog_value_type_hint"
android:textSize="16sp"
android:layout_marginTop="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/titleText" />

<Spinner
android:id="@+id/writeValueTypeSp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="@string/subscribe_dialog_is_urgent_hint"
android:inputType="text"
android:spinnerMode="dropdown"
android:textSize="16sp"
android:layout_marginTop="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/titleValueTypeTv" />

<EditText
android:id="@+id/writeValueEd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="@string/write_dialog_write_value_hint"
android:inputType="text"
android:textSize="16sp"
android:layout_marginBottom="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/writeValueTypeSp" />

<EditText
android:id="@+id/dataVersionEd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="@string/write_dialog_data_version_hint"
android:inputType="text"
android:textSize="16sp"
android:layout_marginBottom="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/writeValueEd" />

<EditText
android:id="@+id/timedRequestTimeoutEd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="@string/write_dialog_timed_request_timeout_hint"
android:inputType="number"
android:textSize="16sp"
android:layout_marginBottom="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dataVersionEd" />

<EditText
android:id="@+id/imTimeoutEd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="@string/write_dialog_im_timeout_hint"
android:inputType="number"
android:textSize="16sp"
android:layout_marginBottom="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/timedRequestTimeoutEd"
app:layout_constraintBottom_toTopOf="@id/exampleHint"/>
<TextView
android:id="@+id/exampleHint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/write_dialog_user_guide_hint"
android:textSize="10sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/imTimeoutEd"
app:layout_constraintBottom_toTopOf="@id/writeBtn"/>

<Button
android:id="@+id/writeBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/write_dialog_write_btn_text"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
10 changes: 10 additions & 0 deletions examples/android/CHIPTool/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -146,13 +146,23 @@
<string name="subscribe_dialog_keep_subscriptions_hint">keep subscriptions (bool)</string>
<string name="subscribe_dialog_is_fabric_filtered_hint">is Fabric Filtered (bool)</string>

<string name="write_dialog_title_text">Write</string>
<string name="write_dialog_write_btn_text">Write</string>
<string name="write_dialog_value_type_hint">Write Value Type</string>
<string name="write_dialog_write_value_hint">Write Value</string>
<string name="write_dialog_data_version_hint">Data Version (Optional)</string>
<string name="write_dialog_timed_request_timeout_hint">Timed Request Timeout</string>
<string name="write_dialog_im_timeout_hint">Timeout</string>
<string name="write_dialog_user_guide_hint">Example:\nIdentifyCluster, IdentifyTime - Type : UInt\nBasicInformationCluster, UserLabel - Type : String\nIf you want to enter an array, separate the value by \',\'.(abc,def)</string>

<string name="endpoint_id_label">Endpoint ID</string>
<string name="cluster_id_label">Cluster ID</string>
<string name="attribute_id_label">Attribute ID</string>
<string name="event_id_label">Event ID</string>
<string name="wildcard_help_label">Leave blank for wildcard</string>
<string name="wildcard_subscribe_btn_text">Subscribe</string>
<string name="wildcard_read_btn_text">Read</string>
<string name="wildcard_write_btn_text">Write</string>
<string name="wildcard_subscribe_event_btn_text">Subscribe Event</string>
<string name="wildcard_read_event_btn_text">Read Event</string>

Expand Down

0 comments on commit 9769516

Please sign in to comment.