Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Enhancement] Provide CHIP APIs coroutine extension functions #110

Open
tetedoie opened this issue Mar 27, 2023 · 1 comment
Open

[Enhancement] Provide CHIP APIs coroutine extension functions #110

tetedoie opened this issue Mar 27, 2023 · 1 comment

Comments

@tetedoie
Copy link

The CHIP APIs are mostly Java callback based APIs but most developers will use Kotlin (and Kotlin coroutines).

In GHSAFM, we have 3 wrappers classes (ChipClient, ClustersHelper and SubscriptionHelper) which are often only adapting the CHIP Java callback based APIs into Kotlin coroutines. In order to avoid that every developer implement and maintain this code on their own, we could provide Kotlin extension functions inside an additional maven artifact.

For example, in ChipClient we have

suspend fun getConnectedDevicePointer(nodeId: Long): Long {
    return suspendCoroutine { continuation ->
      chipDeviceController.getConnectedDevicePointer(
          nodeId,
          object : GetConnectedDeviceCallback {
            override fun onDeviceConnected(devicePointer: Long) {
              Timber.d("Got connected device pointer")
              continuation.resume(devicePointer)
            }

            override fun onConnectionFailure(nodeId: Long, error: Exception) {
              val errorMessage = "Unable to get connected device with nodeId $nodeId."
              Timber.e(errorMessage, error)
              continuation.resumeWithException(IllegalStateException(errorMessage))
            }
          })
    }
  }

(We also have awaitGetConnectedDevicePointer which seams to be duplicate code)

So we could provide an extension function on ChipDeviceController like

suspend fun ChipDeviceController.getConnectedDevicePointer(
    nodeId: Long
): Long = suspendCoroutine {
    getConnectedDevicePointer(
        nodeId,
        object : GetConnectedDeviceCallback {
            override fun onDeviceConnected(devicePointer: Long) {
                it.resume(devicePointer)
            }

            override fun onConnectionFailure(nodeId: Long, error: Exception?) {
                it.resumeWithException(
                    IllegalStateException(
                        "Unable to get connected device with nodeId $nodeId.",
                        error
                    )
                )
            }
        }
    )
}

The same can be done for predefined clusters like for

    public static class DescriptorCluster extends BaseChipCluster {

        [...]

        public void readDeviceTypeListAttribute(DeviceTypeListAttributeCallback callback) {
            [...]
        }

        public void subscribeDeviceTypeListAttribute(DeviceTypeListAttributeCallback callback, int minInterval, int maxInterval) {
            [...]
        }
      
        [...]
  }

We could provide

suspend fun ChipClusters.DescriptorCluster.readDeviceTypeListAttribute(
): List<ChipStructs.DescriptorClusterDeviceTypeStruct> = suspendCoroutine {
    readDeviceTypeListAttribute(
        object : ChipClusters.DescriptorCluster.DeviceTypeListAttributeCallback {
            override fun onSuccess(
                values: List<ChipStructs.DescriptorClusterDeviceTypeStruct>
            ) {
                it.resume(values)
            }

            override fun onError(ex: Exception) {
                it.resumeWithException(ex)
            }
        }
    )
}

fun ChipClusters.DescriptorCluster.subscribeDeviceTypeListAttribute(
    minInterval: Int,
    maxInterval: Int
): Flow<List<ChipStructs.DescriptorClusterDeviceTypeStruct>> = callbackFlow {
    subscribeDeviceTypeListAttribute(
        object : ChipClusters.DescriptorCluster.DeviceTypeListAttributeCallback {
            override fun onSuccess(valueList: MutableList<ChipStructs.DescriptorClusterDeviceTypeStruct>) {
                trySend(valueList)
            }

            override fun onError(ex: java.lang.Exception?) {
                close(ex)
            }
        },
        minInterval,
        maxInterval
    )
}

As both use the same callback type, we could even share the callback creation inside a private function but it's just an implementation detail.

This is an enhancement proposal, the actual CHIP artifacts should be published first. Then it should be implemented in the CHIP repository instead of the GHSAFM repository.

@pierredelisle
Copy link
Contributor

Thanks Paul-Marie. Makes sense.
We should get resolution on #80 soon, and then we should tackle this as you propose.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants