blob: 9afb3aef436fa0cd8aa63fe98f17769353016610 [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.Button
import android.widget.EditText
import android.widget.SeekBar
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import chip.devicecontroller.ChipDeviceController
import chip.devicecontroller.InvokeCallback
import chip.devicecontroller.ReportCallback
import chip.devicecontroller.ResubscriptionAttemptCallback
import chip.devicecontroller.SubscriptionEstablishedCallback
import chip.devicecontroller.model.ChipAttributePath
import chip.devicecontroller.model.ChipEventPath
import chip.devicecontroller.model.InvokeElement
import chip.devicecontroller.model.NodeState
import chip.tlv.TlvWriter
import com.google.chip.chiptool.ChipClient
import com.google.chip.chiptool.GenericChipDeviceListener
import com.google.chip.chiptool.R
import com.google.chip.chiptool.databinding.OnOffClientFragmentBinding
import com.google.chip.chiptool.util.TlvParseUtil
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import chip.devicecontroller.ClusterIDMapping.*
import chip.tlv.AnonymousTag
import chip.tlv.ContextSpecificTag
class OnOffClientFragment : Fragment() {
private val deviceController: ChipDeviceController
get() = ChipClient.getDeviceController(requireContext())
private lateinit var scope: CoroutineScope
private lateinit var addressUpdateFragment: AddressUpdateFragment
private var _binding: OnOffClientFragmentBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = OnOffClientFragmentBinding.inflate(inflater, container, false)
scope = viewLifecycleOwner.lifecycleScope
deviceController.setCompletionListener(ChipControllerCallback())
addressUpdateFragment =
childFragmentManager.findFragmentById(R.id.addressUpdateFragment) as AddressUpdateFragment
binding.onBtn.setOnClickListener { scope.launch { sendOnOffClusterCommand(OnOff.Command.On) } }
binding.offBtn.setOnClickListener { scope.launch { sendOnOffClusterCommand(OnOff.Command.Off) } }
binding.toggleBtn.setOnClickListener { scope.launch { sendOnOffClusterCommand(OnOff.Command.Toggle) } }
binding.readBtn.setOnClickListener { scope.launch { sendReadOnOffClick() } }
binding.showSubscribeDialogBtn.setOnClickListener { showSubscribeDialog() }
binding.levelBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar, i: Int, b: Boolean) {
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {
}
override fun onStopTrackingTouch(seekBar: SeekBar?) {
Toast.makeText(
requireContext(),
"Level is: " + binding.levelBar.progress,
Toast.LENGTH_SHORT
).show()
scope.launch { sendLevelCommandClick() }
}
})
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
private suspend fun sendReadOnOffClick() {
val endpointId = addressUpdateFragment.endpointId
val clusterId = OnOff.ID
val attributeId = OnOff.Attribute.OnOff.id
val attributePath = ChipAttributePath.newInstance(endpointId.toLong(), clusterId, attributeId)
ChipClient.getDeviceController(requireContext()).readPath(object: ReportCallback {
override fun onError(attributePath: ChipAttributePath?, eventPath: ChipEventPath?, ex: java.lang.Exception) {
Log.e(TAG, "Error reading onOff attribute", ex)
}
override fun onReport(nodeState: NodeState?) {
val value = nodeState?.getEndpointState(endpointId)?.getClusterState(clusterId)?.getAttributeState(attributeId)?.value ?: "null"
Log.v(TAG, "On/Off attribute value: $value")
showMessage("On/Off attribute value: $value")
}
}, getConnectedDevicePointer(), listOf(attributePath), null, false, 0 /* imTimeoutMs */)
}
private fun showSubscribeDialog() {
val dialogView = requireActivity().layoutInflater.inflate(R.layout.subscribe_dialog, null)
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)
dialogView.findViewById<Button>(R.id.subscribeBtn).setOnClickListener {
scope.launch {
sendSubscribeOnOffClick(
minIntervalEd.text.toString().toInt(),
maxIntervalEd.text.toString().toInt()
)
requireActivity().runOnUiThread {
dialog.dismiss()
}
}
}
dialog.show()
}
private suspend fun sendSubscribeOnOffClick(minInterval: Int, maxInterval: Int) {
val endpointId = addressUpdateFragment.endpointId
val clusterId = OnOff.ID
val attributeId = OnOff.Attribute.OnOff.id
val attributePath = ChipAttributePath.newInstance(endpointId.toLong(), clusterId, attributeId)
val subscriptionEstablishedCallback =
SubscriptionEstablishedCallback {
subscriptionId ->
Log.i(TAG, "Subscription to device established : ${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") }
deviceController.subscribeToPath(subscriptionEstablishedCallback,
resubscriptionAttemptCallback,
object: ReportCallback {
override fun onError(attributePath: ChipAttributePath?, eventPath: ChipEventPath?, ex: Exception) {
Log.e(TAG, "Error configuring on/off attribute", ex)
}
override fun onReport(nodeState: NodeState?) {
val tlv = nodeState?.getEndpointState(endpointId)?.getClusterState(clusterId)?.getAttributeState(attributeId)?.tlv ?: return
// TODO : Need to be implement poj-to-tlv
val value = TlvParseUtil.decodeBoolean(tlv)
val formatter = SimpleDateFormat("HH:mm:ss", Locale.getDefault())
val time = formatter.format(Calendar.getInstance(Locale.getDefault()).time)
val message = "Subscribed on/off value at $time: ${if (value) "ON" else "OFF"}"
Log.v(TAG, message)
showReportMessage(message)
}
},
getConnectedDevicePointer(),
listOf(attributePath),
null,
minInterval,
maxInterval,
false,
false,
/* imTimeoutMs= */ 0)
}
inner class ChipControllerCallback : GenericChipDeviceListener() {
override fun onConnectDeviceComplete() {}
override fun onCommissioningComplete(nodeId: Long, errorCode: Int) {
Log.d(TAG, "onCommissioningComplete for nodeId $nodeId: $errorCode")
showMessage("Address update complete for nodeId $nodeId with code $errorCode")
}
override fun onNotifyChipConnectionClosed() {
Log.d(TAG, "onNotifyChipConnectionClosed")
}
override fun onCloseBleComplete() {
Log.d(TAG, "onCloseBleComplete")
}
override fun onError(error: Throwable?) {
Log.d(TAG, "onError: $error")
}
}
private suspend fun sendLevelCommandClick() {
// TODO : Need to be implement poj-to-tlv
val tlvWriter = TlvWriter()
tlvWriter.startStructure(AnonymousTag)
tlvWriter.put(ContextSpecificTag(0), binding.levelBar.progress.toUInt())
tlvWriter.put(ContextSpecificTag(1), 0u)
tlvWriter.put(ContextSpecificTag(2), 0u)
tlvWriter.put(ContextSpecificTag(3), 0u)
tlvWriter.endStructure()
val invokeElement = InvokeElement.newInstance(addressUpdateFragment.endpointId.toLong()
, LevelControl.ID
, LevelControl.Command.MoveToLevel.id
, tlvWriter.getEncoded(), null)
deviceController.invoke(object: InvokeCallback {
override fun onError(ex: Exception?) {
showMessage("MoveToLevel command failure $ex")
Log.e(TAG, "MoveToLevel command failure", ex)
}
override fun onResponse(invokeElement: InvokeElement?, successCode: Long) {
Log.e(TAG, "onResponse : $invokeElement, Code : $successCode")
showMessage("MoveToLevel command success")
}
}, getConnectedDevicePointer(), invokeElement, 0, 0)
}
private suspend fun sendOnOffClusterCommand(commandId: OnOff.Command) {
// TODO : Need to be implement poj-to-tlv
val tlvWriter = TlvWriter()
tlvWriter.startStructure(AnonymousTag)
tlvWriter.endStructure()
val invokeElement = InvokeElement.newInstance(addressUpdateFragment.endpointId.toLong()
, OnOff.ID
, commandId.id
, tlvWriter.getEncoded(), null)
deviceController.invoke(object: InvokeCallback {
override fun onError(ex: Exception?) {
showMessage("${commandId.name} command failure $ex")
Log.e(TAG, "${commandId.name} command failure", ex)
}
override fun onResponse(invokeElement: InvokeElement?, successCode: Long) {
Log.e(TAG, "onResponse : $invokeElement, Code : $successCode")
showMessage("${commandId.name} command success")
}
}, getConnectedDevicePointer(), invokeElement, 0, 0)
}
private suspend fun getConnectedDevicePointer(): Long {
return ChipClient.getConnectedDevicePointer(requireContext(), addressUpdateFragment.deviceId)
}
private fun showMessage(msg: String) {
requireActivity().runOnUiThread {
binding.commandStatusTv.text = msg
}
}
private fun showReportMessage(msg: String) {
requireActivity().runOnUiThread {
binding.reportStatusTv.text = msg
}
}
override fun onResume() {
super.onResume()
addressUpdateFragment.endpointId = ON_OFF_CLUSTER_ENDPOINT
}
companion object {
private const val TAG = "OnOffClientFragment"
private const val ON_OFF_CLUSTER_ENDPOINT = 1
private const val LEVEL_CONTROL_CLUSTER_ENDPOINT = 1
fun newInstance(): OnOffClientFragment = OnOffClientFragment()
}
}