diff --git a/docs/examples/index.md b/docs/examples/index.md
index 83a1a6f936c90c..e5144f81885039 100644
--- a/docs/examples/index.md
+++ b/docs/examples/index.md
@@ -87,6 +87,15 @@ darwin-framework-tool/README
java-matter-controller/README
```
+## Kotlin matter controller example
+
+```{toctree}
+:glob:
+:maxdepth: 1
+
+kotlin-matter-controller/README
+```
+
## Virtual Device App example
```{toctree}
diff --git a/examples/kotlin-matter-controller/.gitignore b/examples/kotlin-matter-controller/.gitignore
new file mode 100644
index 00000000000000..d59e52c6fd3f22
--- /dev/null
+++ b/examples/kotlin-matter-controller/.gitignore
@@ -0,0 +1,20 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
+
+# Shared libs & JAR libs (those libs are copied into source tree for easy Android build).
+*.so
+*.jar
+*.map
diff --git a/examples/kotlin-matter-controller/.gn b/examples/kotlin-matter-controller/.gn
new file mode 100644
index 00000000000000..5cd171abcda9ff
--- /dev/null
+++ b/examples/kotlin-matter-controller/.gn
@@ -0,0 +1,25 @@
+# Copyright (c) 2020-2022 Project CHIP Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import("//build_overrides/build.gni")
+
+# The location of the build configuration file.
+buildconfig = "${build_root}/config/BUILDCONFIG.gn"
+
+# CHIP uses angle bracket includes.
+check_system_includes = true
+
+default_args = {
+ import("//args.gni")
+}
diff --git a/examples/kotlin-matter-controller/BUILD.gn b/examples/kotlin-matter-controller/BUILD.gn
new file mode 100644
index 00000000000000..b13208e65ef749
--- /dev/null
+++ b/examples/kotlin-matter-controller/BUILD.gn
@@ -0,0 +1,56 @@
+# Copyright (c) 2022-2023 Project CHIP Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import("//build_overrides/build.gni")
+import("//build_overrides/chip.gni")
+
+import("${chip_root}/build/chip/java/rules.gni")
+import("${chip_root}/build/chip/tools.gni")
+
+kotlin_binary("kotlin-matter-controller") {
+ output_name = "kotlin-matter-controller"
+ deps = [
+ "${chip_root}/src/controller/java:kotlin_matter_controller",
+ "${chip_root}/src/controller/java:onboarding_payload",
+ "${chip_root}/third_party/java_deps:annotation",
+ ]
+
+ sources = [
+ "java/src/com/matter/controller/Main.kt",
+ "java/src/com/matter/controller/commands/common/Argument.kt",
+ "java/src/com/matter/controller/commands/common/ArgumentType.kt",
+ "java/src/com/matter/controller/commands/common/Command.kt",
+ "java/src/com/matter/controller/commands/common/CommandManager.kt",
+ "java/src/com/matter/controller/commands/common/CredentialsIssuer.kt",
+ "java/src/com/matter/controller/commands/common/FutureResult.kt",
+ "java/src/com/matter/controller/commands/common/IPAddress.kt",
+ "java/src/com/matter/controller/commands/common/MatterCommand.kt",
+ "java/src/com/matter/controller/commands/common/RealResult.kt",
+ "java/src/com/matter/controller/commands/pairing/DiscoveryFilterType.kt",
+ "java/src/com/matter/controller/commands/pairing/PairOnNetworkLongCommand.kt",
+ "java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImInvokeCommand.kt",
+ "java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImReadCommand.kt",
+ "java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImSubscribeCommand.kt",
+ "java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImWriteCommand.kt",
+ "java/src/com/matter/controller/commands/pairing/PairingCommand.kt",
+ "java/src/com/matter/controller/commands/pairing/PairingModeType.kt",
+ "java/src/com/matter/controller/commands/pairing/PairingNetworkType.kt",
+ ]
+
+ kotlinc_flags = [ "-Xlint:deprecation" ]
+}
+
+group("default") {
+ deps = [ ":kotlin-matter-controller" ]
+}
diff --git a/examples/kotlin-matter-controller/Manifest.txt b/examples/kotlin-matter-controller/Manifest.txt
new file mode 100644
index 00000000000000..2ef5017b51334a
--- /dev/null
+++ b/examples/kotlin-matter-controller/Manifest.txt
@@ -0,0 +1,2 @@
+Main-Class: com.matter.controller.MainKt
+Class-Path: ../lib/third_party/connectedhomeip/src/controller/java/*.jar ../lib/third_party/connectedhomeip/third_party/java_deps/stub_src/Android.jar ../lib/third_party/connectedhomeip/third_party/java_deps/json-20220924.jar ../lib/third_party/connectedhomeip/third_party/java_deps/jsr305-3.0.2.jar ../lib/third_party/connectedhomeip/third_party/java_deps/kotlin-stdlib-1.8.20.jar ../lib/third_party/connectedhomeip/third_party/java_deps/kotlinx-coroutines-core-jvm-1.7.3.jar
diff --git a/examples/kotlin-matter-controller/README.md b/examples/kotlin-matter-controller/README.md
new file mode 100644
index 00000000000000..c4e84cbd5982a4
--- /dev/null
+++ b/examples/kotlin-matter-controller/README.md
@@ -0,0 +1,118 @@
+# Matter Controller Kotlin App Example
+
+This is a Matter Controller Kotlin app that can be used to commission and
+control Matter accessory devices.
+
+
+
+- [Matter Controller Kotlin App Example](#matter-controller-kotlin-app-example)
+ - [Requirements for building](#requirements-for-building)
+ - [Preparing for build](#preparing-for-build)
+ - [Building & Running the app](#building--running-the-app)
+
+
+
+## Requirements for building
+
+You need to have the following two software installed on your Ubuntu system:
+
+1. Java Runtime Environment (JRE)
+2. Java Development Kit (JDK)
+
+openjdk version "11.0.20.1" or above is needed
+
+First, check if we have the Java Runtime Environment and Java Development Kit
+installed on our system or not.
+
+```shell
+java -version
+```
+
+If not, you can install the Java Runtime Environment and Java Development Kit on
+your system through the following command as root:
+
+```
+sudo apt install default-jre
+sudo apt install default-jdk
+```
+
+You also need to install kotlin compiler on your Ubuntu system:
+
+kotlin compiler version 1.8.10 or above is needed to compile
+kotlin-matter-controller, if you already have lower version kotlin compiler
+installed on your Ubuntu from apt,
+you need to remove the Kotlin compiler package, run the following command:
+
+```shell
+sudo apt-get remove kotlin
+```
+
+Wait for the removal process to complete. Once it's done, the Kotlin compiler
+will be removed from your system.
+
+(Optional) If you want to remove any configuration files associated with Kotlin,
+run the following command:
+
+```shell
+sudo apt-get purge kotlin
+```
+
+Install kotlin compiler 1.8.10 or above, such as
+[kotlin-compiler-1.8.10-url](https://github.com/JetBrains/kotlin/releases/download/v1.8.10/kotlin-compiler-1.8.10.zip)
+
+```shell
+cd /usr/lib \
+&& sudo wget -q [kotlin-compiler-1.8.10-url] \
+&& sudo unzip kotlin-compiler-*.zip \
+&& sudo rm kotlin-compiler-*.zip \
+&& sudo rm -f kotlinc/bin/*.bat
+```
+
+Add a directory to PATH permanently by editing the `.bashrc` file located in the
+Home directory. Follow these steps:
+
+1. Open the `.bashrc` file using a text editor.
+2. Go to the end of the file.
+3. Paste the export syntax at the end of the file.
+
+```shell
+export PATH="/usr/lib/kotlinc/bin:$PATH"
+```
+
+### Linux
+
+```shell
+export JAVA_PATH=[JDK path]
+```
+
+
+
+## Preparing for build
+
+Complete the following steps to prepare the Matter build:
+
+1. Check out the Matter repository.
+
+2. Run bootstrap (**only required first time**)
+
+ ```shell
+ source scripts/bootstrap.sh
+ ```
+
+## Building & Running the app
+
+This is the simplest option. In the command line, run the following command from
+the top Matter directory:
+
+```shell
+./scripts/build/build_examples.py --target linux-x64-kotlin-matter-controller build
+```
+
+The Java executable file `kotlin-matter-controller` will be generated at
+`out/android-x86-kotlin-matter-controller/bin/`
+
+Run the kotlin-matter-controller
+
+```shell
+java -Djava.library.path=../lib/jni -jar kotlin-matter-controller
+```
diff --git a/examples/kotlin-matter-controller/args.gni b/examples/kotlin-matter-controller/args.gni
new file mode 100644
index 00000000000000..9b0c312fbdc837
--- /dev/null
+++ b/examples/kotlin-matter-controller/args.gni
@@ -0,0 +1,26 @@
+# Copyright (c) 2020-2022 Project CHIP Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import("//build_overrides/chip.gni")
+
+import("${chip_root}/config/standalone/args.gni")
+
+chip_device_project_config_include = ""
+chip_project_config_include = ""
+chip_system_project_config_include = ""
+
+chip_project_config_include_dirs =
+ [ "${chip_root}/examples/kotlin-matter-controller/include" ]
+chip_project_config_include_dirs += [ "${chip_root}/config/standalone" ]
+chip_stack_lock_tracking = "fatal"
diff --git a/examples/kotlin-matter-controller/build_overrides b/examples/kotlin-matter-controller/build_overrides
new file mode 120000
index 00000000000000..b430cf6a2e6391
--- /dev/null
+++ b/examples/kotlin-matter-controller/build_overrides
@@ -0,0 +1 @@
+../build_overrides
\ No newline at end of file
diff --git a/examples/kotlin-matter-controller/include/CHIPProjectAppConfig.h b/examples/kotlin-matter-controller/include/CHIPProjectAppConfig.h
new file mode 100644
index 00000000000000..09183cc4485256
--- /dev/null
+++ b/examples/kotlin-matter-controller/include/CHIPProjectAppConfig.h
@@ -0,0 +1,56 @@
+/*
+ *
+ * Copyright (c) 2020-2022 Project CHIP Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CHIPPROJECTCONFIG_H
+#define CHIPPROJECTCONFIG_H
+
+#define CHIP_CONFIG_MAX_FABRICS 17
+
+#define CHIP_CONFIG_EVENT_LOGGING_NUM_EXTERNAL_CALLBACKS 2
+
+// Enable support functions for parsing command-line arguments
+#define CHIP_CONFIG_ENABLE_ARG_PARSER 1
+
+// Use a default pairing code if one hasn't been provisioned in flash.
+#define CHIP_DEVICE_CONFIG_USE_TEST_SETUP_PIN_CODE 20202021
+#define CHIP_DEVICE_CONFIG_USE_TEST_SETUP_DISCRIMINATOR 0xF00
+
+// Enable reading DRBG seed data from /dev/(u)random.
+// This is needed for test applications and the CHIP device manager to function
+// properly when CHIP_CONFIG_RNG_IMPLEMENTATION_CHIPDRBG is enabled.
+#define CHIP_CONFIG_DEV_RANDOM_DRBG_SEED 1
+
+// For convenience, Chip Security Test Mode can be enabled and the
+// requirement for authentication in various protocols can be disabled.
+//
+// WARNING: These options make it possible to circumvent basic Chip security functionality,
+// including message encryption. Because of this they MUST NEVER BE ENABLED IN PRODUCTION BUILDS.
+//
+#define CHIP_CONFIG_SECURITY_TEST_MODE 0
+
+#define CHIP_CONFIG_ENABLE_UPDATE 1
+
+#define CHIP_SYSTEM_CONFIG_PACKETBUFFER_POOL_SIZE 0
+
+#define CHIP_CONFIG_DATA_MANAGEMENT_CLIENT_EXPERIMENTAL 1
+
+#define CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY 1
+
+// Enable some test-only interaction model APIs.
+#define CONFIG_BUILD_FOR_HOST_UNIT_TEST 1
+
+#endif /* CHIPPROJECTCONFIG_H */
diff --git a/examples/kotlin-matter-controller/java/src/com/matter/controller/Main.kt b/examples/kotlin-matter-controller/java/src/com/matter/controller/Main.kt
new file mode 100644
index 00000000000000..997c1b92b0ebb4
--- /dev/null
+++ b/examples/kotlin-matter-controller/java/src/com/matter/controller/Main.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2023 Project CHIP Authors
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.matter.controller
+
+import com.matter.controller.commands.common.Command
+import com.matter.controller.commands.common.CommandManager
+import com.matter.controller.commands.common.CredentialsIssuer
+import com.matter.controller.commands.pairing.PairOnNetworkLongCommand
+import com.matter.controller.commands.pairing.PairOnNetworkLongImInvokeCommand
+import com.matter.controller.commands.pairing.PairOnNetworkLongImReadCommand
+import com.matter.controller.commands.pairing.PairOnNetworkLongImSubscribeCommand
+import com.matter.controller.commands.pairing.PairOnNetworkLongImWriteCommand
+import matter.controller.ControllerParams
+import matter.controller.MatterController
+import matter.controller.MatterControllerImpl
+
+private fun getPairingCommands(
+ controller: MatterController,
+ credentialsIssuer: CredentialsIssuer
+): List {
+ return listOf(
+ PairOnNetworkLongCommand(controller, credentialsIssuer),
+ )
+}
+
+private fun getImCommands(
+ controller: MatterController,
+ credentialsIssuer: CredentialsIssuer
+): List {
+ return listOf(
+ PairOnNetworkLongImReadCommand(controller, credentialsIssuer),
+ PairOnNetworkLongImSubscribeCommand(controller, credentialsIssuer),
+ PairOnNetworkLongImWriteCommand(controller, credentialsIssuer),
+ PairOnNetworkLongImInvokeCommand(controller, credentialsIssuer),
+ )
+}
+
+fun main(args: Array) {
+ val controller: MatterController = MatterControllerImpl(ControllerParams(countryCode = "US"))
+ val credentialsIssuer = CredentialsIssuer()
+ val commandManager = CommandManager()
+
+ commandManager.register("pairing", getPairingCommands(controller, credentialsIssuer))
+ commandManager.register("im", getImCommands(controller, credentialsIssuer))
+
+ try {
+ commandManager.run(args)
+ } catch (e: Exception) {
+ println("Run command failed with exception: " + e.message)
+ controller.close()
+ System.exit(1)
+ }
+
+ controller.close()
+}
diff --git a/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/Argument.kt b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/Argument.kt
new file mode 100644
index 00000000000000..7f9f11d183ec80
--- /dev/null
+++ b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/Argument.kt
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2023 Project CHIP Authors
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.matter.controller.commands.common
+
+import java.net.InetAddress
+import java.net.UnknownHostException
+import java.util.concurrent.atomic.AtomicBoolean
+import java.util.concurrent.atomic.AtomicInteger
+import java.util.concurrent.atomic.AtomicLong
+
+// TODO Issue #29632:Refactor class Argument to have specializations for various types.
+class Argument {
+ val name: String
+ val type: ArgumentType
+ private val minValue: Long
+ private val maxValue: Long
+ val value: Any
+ val desc: String?
+ val isOptional: Boolean
+
+ constructor(name: String, value: IPAddress, optional: Boolean) {
+ this.name = name
+ type = ArgumentType.ADDRESS
+ minValue = 0
+ maxValue = 0
+ this.value = value
+ desc = null
+ isOptional = optional
+ }
+
+ constructor(name: String, value: StringBuffer, desc: String?, optional: Boolean) {
+ this.name = name
+ type = ArgumentType.STRING
+ minValue = 0
+ maxValue = 0
+ this.value = value
+ this.desc = desc
+ isOptional = optional
+ }
+
+ constructor(name: String, value: AtomicBoolean, desc: String?, optional: Boolean) {
+ this.name = name
+ type = ArgumentType.BOOL
+ minValue = 0
+ maxValue = 0
+ this.value = value
+ this.desc = desc
+ isOptional = optional
+ }
+
+ constructor(
+ name: String,
+ min: Short,
+ max: Short,
+ value: AtomicInteger,
+ desc: String?,
+ optional: Boolean
+ ) {
+ this.name = name
+ type = ArgumentType.NUMBER_INT16
+ minValue = min.toLong()
+ maxValue = max.toLong()
+ this.value = value
+ this.desc = desc
+ isOptional = optional
+ }
+
+ constructor(
+ name: String,
+ min: Int,
+ max: Int,
+ value: AtomicInteger,
+ desc: String?,
+ optional: Boolean
+ ) {
+ this.name = name
+ type = ArgumentType.NUMBER_INT32
+ minValue = min.toLong()
+ maxValue = max.toLong()
+ this.value = value
+ this.desc = desc
+ isOptional = optional
+ }
+
+ constructor(
+ name: String,
+ min: Short,
+ max: Short,
+ value: AtomicLong,
+ desc: String?,
+ optional: Boolean
+ ) {
+ this.name = name
+ type = ArgumentType.NUMBER_INT32
+ minValue = min.toLong()
+ maxValue = max.toLong()
+ this.value = value
+ this.desc = desc
+ isOptional = optional
+ }
+
+ constructor(
+ name: String,
+ min: Long,
+ max: Long,
+ value: AtomicLong,
+ desc: String?,
+ optional: Boolean
+ ) {
+ this.name = name
+ type = ArgumentType.NUMBER_INT64
+ minValue = min
+ maxValue = max
+ this.value = value
+ this.desc = desc
+ isOptional = optional
+ }
+
+ fun setValue(value: String) {
+ var isValidArgument = false
+ when (type) {
+ ArgumentType.ATTRIBUTE -> {
+ val str = this.value as String
+ isValidArgument = value == str
+ }
+ ArgumentType.NUMBER_INT16 -> {
+ val numShort = this.value as AtomicInteger
+ numShort.set(value.toInt())
+ isValidArgument = numShort.toInt() >= minValue && numShort.toInt() <= maxValue
+ }
+ ArgumentType.NUMBER_INT32 -> {
+ val num = this.value as AtomicInteger
+ num.set(value.toInt())
+ isValidArgument = num.toInt() >= minValue && num.toInt() <= maxValue
+ }
+ ArgumentType.NUMBER_INT64 -> {
+ val numLong = this.value as AtomicLong
+ numLong.set(value.toLong())
+ isValidArgument = numLong.toInt() >= minValue && numLong.toInt() <= maxValue
+ }
+ ArgumentType.STRING -> {
+ val stringBuffer = this.value as StringBuffer
+ stringBuffer.append(value)
+ val str = stringBuffer.toString()
+ isValidArgument = value == str
+ }
+ ArgumentType.BOOL -> {
+ val atomicBoolean = this.value as AtomicBoolean
+ try {
+ atomicBoolean.set(value.toBoolean())
+ isValidArgument = true
+ } catch (e: Exception) {
+ isValidArgument = false
+ }
+ }
+ ArgumentType.ADDRESS ->
+ isValidArgument =
+ try {
+ val ipAddress = this.value as IPAddress
+ ipAddress.address = InetAddress.getByName(value)
+ true
+ } catch (e: UnknownHostException) {
+ false
+ }
+ else -> {}
+ }
+ require(isValidArgument) { "Invalid argument " + name + ": " + value }
+ }
+}
diff --git a/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/ArgumentType.kt b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/ArgumentType.kt
new file mode 100644
index 00000000000000..acd31fbb0ac25a
--- /dev/null
+++ b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/ArgumentType.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2023 Project CHIP Authors
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.matter.controller.commands.common
+
+enum class ArgumentType {
+ NUMBER_INT8,
+ NUMBER_INT16,
+ NUMBER_INT32,
+ NUMBER_INT64,
+ FLOAT,
+ DOUBLE,
+ BOOL,
+ STRING,
+ CHARSTRING,
+ OCTETSTRING,
+ ATTRIBUTE,
+ ADDRESS,
+ COMPLEX,
+ CUSTOM,
+ VECTOR_BOOL,
+ VECTOR16,
+ VECTOR32,
+ VECTOR_CUSTOM
+}
diff --git a/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/Command.kt b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/Command.kt
new file mode 100644
index 00000000000000..1b05e0205f9a0b
--- /dev/null
+++ b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/Command.kt
@@ -0,0 +1,259 @@
+/*
+ * Copyright (c) 2023 Project CHIP Authors
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.matter.controller.commands.common
+
+import java.util.ArrayList
+import java.util.concurrent.atomic.AtomicBoolean
+import java.util.concurrent.atomic.AtomicInteger
+import java.util.concurrent.atomic.AtomicLong
+
+/**
+ * @brief Matter Controller command
+ * @details The base class of all the commands the Matter Controller supports, which are actions
+ * that may be performed. Commands are verb-like, such as pair a Matter device or discover Matter
+ * devices from the environment.
+ */
+abstract class Command(val name: String, val helpText: String? = null) {
+ private val arguments = ArrayList()
+
+ fun getArgumentName(index: Int): String {
+ return arguments[index].name
+ }
+
+ fun getArgumentsCount(): Int {
+ return arguments.size
+ }
+
+ fun getArgumentIsOptional(index: Int): Boolean {
+ return arguments[index].isOptional
+ }
+
+ /**
+ * @return A pointer to an Optional where the Attribute argument will be stored
+ * @brief Get attribute argument if it exists, there is at most one Attribute argument per command
+ */
+ fun getAttribute(): String? {
+ return arguments.find { arg -> arg.type === ArgumentType.ATTRIBUTE }?.value as? String
+ }
+
+ /**
+ * @return A pointer to an Optional where the argument description will be stored
+ * @brief Get argument description if it exists
+ */
+ fun getArgumentDescription(index: Int): String? {
+ return arguments[index].desc
+ }
+
+ fun addArgumentToList(arg: Argument) {
+ if (arg.isOptional || arguments.isEmpty()) {
+ // Safe to just append to the end of a list.
+ arguments.add(arg)
+ return
+ }
+
+ // mandatory arg needs to be inserted before the optional arguments.
+ var index = 0
+ while (index < arguments.size && !arguments[index].isOptional) {
+ index++
+ }
+
+ // Insert before the first optional arg.
+ arguments.add(index, arg)
+ }
+
+ /**
+ * @param name The name that will be displayed in the command help
+ * @param out A pointer to a AtomicBoolean where the argv value will be stored
+ * @param desc The description of the argument that will be displayed in the command help
+ * @param optional Indicate if an optional argument
+ * @return The number of arguments currently added to the command
+ * @brief Add a bool command argument
+ */
+ fun addArgument(name: String, out: AtomicBoolean, desc: String?, optional: Boolean) {
+ val arg = Argument(name, out, desc, optional)
+ addArgumentToList(arg)
+ }
+
+ /**
+ * @param name The name that will be displayed in the command help
+ * @param min The minimum value of the argv value
+ * @param max The minimum value of the argv value
+ * @param out A pointer to a AtomicInteger where the argv value will be stored
+ * @param desc The description of the argument that will be displayed in the command help
+ * @param optional Indicate if an optional argument
+ * @return The number of arguments currently added to the command
+ * @brief Add a short command argument
+ */
+ fun addArgument(
+ name: String,
+ min: Short,
+ max: Short,
+ out: AtomicInteger,
+ desc: String?,
+ optional: Boolean
+ ) {
+ val arg = Argument(name, min, max, out, desc, optional)
+ addArgumentToList(arg)
+ }
+
+ /**
+ * @param name The name that will be displayed in the command help
+ * @param min The minimum value of the argv value
+ * @param max The minimum value of the argv value
+ * @param out A pointer to a AtomicInteger where the argv value will be stored
+ * @param desc The description of the argument that will be displayed in the command help
+ * @param optional Indicate if an optional argument
+ * @return The number of arguments currently added to the command
+ * @brief Add an int command argument
+ */
+ fun addArgument(
+ name: String,
+ min: Int,
+ max: Int,
+ out: AtomicInteger,
+ desc: String?,
+ optional: Boolean
+ ) {
+ val arg = Argument(name, min, max, out, desc, optional)
+ addArgumentToList(arg)
+ }
+
+ /**
+ * @param name The name that will be displayed in the command help
+ * @param min The minimum value of the argv value
+ * @param max The minimum value of the argv value
+ * @param out A pointer to a MutableInteger where the argv value will be stored
+ * @param desc The description of the argument that will be displayed in the command help
+ * @param optional Indicate if an optional argument
+ * @return The number of arguments currently added to the command
+ * @brief Add a long Integer command argument
+ */
+ fun addArgument(
+ name: String,
+ min: Short,
+ max: Short,
+ out: AtomicLong,
+ desc: String?,
+ optional: Boolean
+ ) {
+ val arg = Argument(name, min, max, out, desc, optional)
+ addArgumentToList(arg)
+ }
+
+ /**
+ * @param name The name that will be displayed in the command help
+ * @param min The minimum value of the argv value
+ * @param max The minimum value of the argv value
+ * @param out A pointer to a AtomicLong where the argv value will be stored
+ * @param desc The description of the argument that will be displayed in the command help
+ * @param optional Indicate if an optional argument
+ * @return The number of arguments currently added to the command
+ * @brief Add a long Integer command argument
+ */
+ fun addArgument(
+ name: String,
+ min: Long,
+ max: Long,
+ out: AtomicLong,
+ desc: String?,
+ optional: Boolean
+ ) {
+ val arg = Argument(name, min, max, out, desc, optional)
+ addArgumentToList(arg)
+ }
+
+ /**
+ * @param name The name that will be displayed in the command help
+ * @param out A pointer to a IPAddress where the argv value will be stored
+ * @param optional Indicate if an optional argument
+ * @return The number of arguments currently added to the command
+ * @brief Add an IP address command argument
+ */
+ fun addArgument(name: String, out: IPAddress, optional: Boolean) {
+ val arg = Argument(name, out, optional)
+ addArgumentToList(arg)
+ }
+
+ /**
+ * @param name The name that will be displayed in the command help
+ * @param out A pointer to a StringBuffer where the argv value will be stored
+ * @param desc The description of the argument that will be displayed in the command help
+ * @param optional Indicate if an optional argument
+ * @return The number of arguments currently added to the command
+ * @brief Add a String command argument
+ */
+ fun addArgument(name: String, out: StringBuffer, desc: String?, optional: Boolean) {
+ val arg = Argument(name, out, desc, optional)
+ addArgumentToList(arg)
+ }
+
+ /**
+ * @param args Supplied command-line arguments as an array of String objects.
+ * @brief Initialize command arguments
+ */
+ fun setArgumentValues(args: Array) {
+ val argc = args.size
+ var mandatoryArgsCount = 0
+ var currentIndex = 0
+ for (arg in arguments) {
+ if (!arg.isOptional) {
+ mandatoryArgsCount++
+ }
+ }
+ require(argc >= mandatoryArgsCount) {
+ "setArgumentValues: Wrong arguments number: $argc instead of $mandatoryArgsCount"
+ }
+
+ // Initialize mandatory arguments
+ for (i in 0 until mandatoryArgsCount) {
+ setArgumentValue(currentIndex++, args[i])
+ }
+
+ // Initialize optional arguments
+ // Optional arguments expect a name and a value, so it is increased by 2 on every step.
+ var i = mandatoryArgsCount
+ while (i < argc) {
+
+ // optional arguments starts with OPTIONAL_ARGUMENT_PREFIX
+ require(
+ !(args[i].length <= OPTIONAL_ARGUMENT_PREFIX_LENGTH &&
+ !args[i].startsWith(OPTIONAL_ARGUMENT_PREFIX))
+ ) {
+ "setArgumentValues: Invalid optional argument: " + args[i]
+ }
+ if (args[i].substring(OPTIONAL_ARGUMENT_PREFIX_LENGTH) == arguments[currentIndex].name) {
+ require(i + 1 < argc) {
+ "setArgumentValues: Optional argument " + args[i] + " missing value"
+ }
+ setArgumentValue(currentIndex++, args[i + 1])
+ }
+ i += 2
+ }
+ }
+
+ private fun setArgumentValue(argIndex: Int, argValue: String) {
+ arguments[argIndex].setValue(argValue)
+ }
+
+ @Throws(Exception::class) abstract fun run()
+
+ companion object {
+ private const val OPTIONAL_ARGUMENT_PREFIX = "--"
+ private const val OPTIONAL_ARGUMENT_PREFIX_LENGTH = 2
+ }
+}
diff --git a/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/CommandManager.kt b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/CommandManager.kt
new file mode 100644
index 00000000000000..36389a743cf8c5
--- /dev/null
+++ b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/CommandManager.kt
@@ -0,0 +1,313 @@
+/*
+ * Copyright (c) 2023 Project CHIP Authors
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.matter.controller.commands.common
+
+import java.util.Arrays
+import java.util.HashMap
+import java.util.logging.Level
+import java.util.logging.Logger
+import kotlin.collections.MutableMap
+
+class CommandManager {
+ private val clusters: MutableMap> = HashMap()
+
+ fun register(clusterName: String, commandsList: List) {
+ clusters[clusterName] = commandsList
+ }
+
+ @Throws(Exception::class)
+ fun run(args: Array) {
+ val command: Command?
+ if (args.size < 1) {
+ logger.log(Level.INFO, "Missing cluster name")
+ showHelpInfo()
+ return
+ }
+ val commands = clusters[args[0]]
+ if (commands == null) {
+ logger.log(Level.INFO, "Unknown cluster: " + args[0])
+ showHelpInfo()
+ return
+ }
+ if (args.size < 2) {
+ logger.log(Level.INFO, "Missing command name")
+ showCluster(args[0], commands)
+ return
+ }
+ if (!isGlobalCommand(args[0])) {
+ command = getCommand(commands, args[1])
+ if (command == null) {
+ System.out.printf("Unknown command: %s", args[1])
+ showCluster(args[0], commands)
+ throw IllegalArgumentException()
+ }
+ } else if (isEventCommand(args[1])) {
+ if (args.size < 3) {
+ logger.log(Level.INFO, "Missing event name")
+ showClusterEvents(args[0], args[1], commands)
+ throw IllegalArgumentException()
+ }
+ command = getGlobalCommand(commands, args[1], args[2])
+ if (command == null) {
+ logger.log(Level.INFO, "Unknown event: " + args[2])
+ showClusterEvents(args[0], args[1], commands)
+ throw IllegalArgumentException()
+ }
+ } else {
+ if (args.size < 3) {
+ logger.log(Level.INFO, "Missing attribute name")
+ showClusterAttributes(args[0], args[1], commands)
+ throw IllegalArgumentException()
+ }
+ command = getGlobalCommand(commands, args[1], args[2])
+ if (command == null) {
+ logger.log(Level.INFO, "Unknown attribute: " + args[2])
+ showClusterAttributes(args[0], args[1], commands)
+ throw IllegalArgumentException()
+ }
+ }
+
+ // need skip over binary and command name and only get arguments
+ val temp = Arrays.copyOfRange(args, 2, args.size)
+ try {
+ command.setArgumentValues(temp)
+ } catch (e: IllegalArgumentException) {
+ logger.log(Level.INFO, "Arguments init failed with exception: " + e.message)
+ showCommand(args[0], command)
+ System.exit(1)
+ }
+ command.run()
+ }
+
+ private fun isAttributeCommand(commandName: String): Boolean {
+ return commandName == "read" || commandName == "write" || commandName == "subscribe"
+ }
+
+ private fun isEventCommand(commandName: String): Boolean {
+ return commandName == "read-event" || commandName == "subscribe-event"
+ }
+
+ private fun isGlobalCommand(commandName: String): Boolean {
+ return isAttributeCommand(commandName) || isEventCommand(commandName)
+ }
+
+ private fun getCommand(commands: List, commandName: String): Command? {
+ for (command in commands) {
+ if (commandName == command.name) {
+ return command
+ }
+ }
+ return null
+ }
+
+ private fun getGlobalCommand(
+ commands: List,
+ commandName: String,
+ attributeName: String
+ ): Command? {
+ for (command in commands) {
+ if (commandName == command.name && attributeName == command.getAttribute()) {
+ return command
+ }
+ }
+ return null
+ }
+
+ private fun showHelpInfo() {
+ logger.log(Level.INFO, "Usage:")
+ logger.log(Level.INFO, " cluster_name command_name [param1 param2 ...]")
+ logger.log(Level.INFO, "\n")
+ logger.log(
+ Level.INFO,
+ " +-------------------------------------------------------------------------------------+"
+ )
+ logger.log(
+ Level.INFO,
+ " | Clusters: |"
+ )
+ logger.log(
+ Level.INFO,
+ " +-------------------------------------------------------------------------------------+"
+ )
+ for (key in clusters.keys) {
+ System.out.printf(" | * %-82s|\n", key)
+ }
+ logger.log(
+ Level.INFO,
+ " +-------------------------------------------------------------------------------------+"
+ )
+ }
+
+ private fun showCluster(clusterName: String, commands: List) {
+ logger.log(Level.INFO, "Usage:")
+ logger.log(
+ Level.INFO,
+ " kotlin-matter-controller $clusterName command_name [param1 param2 ...]"
+ )
+ logger.log(Level.INFO, "\n")
+ logger.log(
+ Level.INFO,
+ " +-------------------------------------------------------------------------------------+"
+ )
+ logger.log(
+ Level.INFO,
+ " | Commands: |"
+ )
+ logger.log(
+ Level.INFO,
+ " +-------------------------------------------------------------------------------------+"
+ )
+ var readCommand = false
+ var writeCommand = false
+ var subscribeCommand = false
+ var readEventCommand = false
+ var subscribeEventCommand = false
+ for (command in commands) {
+ var shouldPrint = true
+ val cmdName = command.name
+ if (isGlobalCommand(cmdName)) {
+ if (cmdName == "read" && !readCommand) {
+ readCommand = true
+ } else if (cmdName == "write" && !writeCommand) {
+ writeCommand = true
+ } else if (cmdName == "subscribe" && !subscribeCommand) {
+ subscribeCommand = true
+ } else if (cmdName == "read-event" && !readEventCommand) {
+ readEventCommand = true
+ } else if (cmdName == "subscribe-event" && !subscribeEventCommand) {
+ subscribeEventCommand = true
+ } else {
+ shouldPrint = false
+ }
+ }
+ if (shouldPrint) {
+ System.out.printf(" | * %-82s|\n", cmdName)
+ }
+ }
+ logger.log(
+ Level.INFO,
+ " +-------------------------------------------------------------------------------------+\n"
+ )
+ }
+
+ private fun showClusterAttributes(
+ clusterName: String,
+ commandName: String,
+ commands: List
+ ) {
+ logger.log(Level.INFO, "Usage:")
+ System.out.printf(
+ " kotlin-matter-controller %s %s attribute-name [param1 param2 ...]\n",
+ clusterName,
+ commandName
+ )
+ logger.log(Level.INFO, "\n")
+ logger.log(
+ Level.INFO,
+ " +-------------------------------------------------------------------------------------+"
+ )
+ logger.log(
+ Level.INFO,
+ " | Attributes: |"
+ )
+ logger.log(
+ Level.INFO,
+ " +-------------------------------------------------------------------------------------+"
+ )
+ for (command in commands) {
+ if (commandName == command.name) {
+ System.out.printf(" | * %-82s|\n", command.getAttribute())
+ }
+ }
+ logger.log(
+ Level.INFO,
+ " +-------------------------------------------------------------------------------------+"
+ )
+ }
+
+ private fun showClusterEvents(clusterName: String, commandName: String, commands: List) {
+ logger.log(Level.INFO, "Usage:")
+ System.out.printf(
+ " kotlin-matter-controller %s %s event-name [param1 param2 ...]\n",
+ clusterName,
+ commandName
+ )
+ logger.log(Level.INFO, "\n")
+ logger.log(
+ Level.INFO,
+ " +-------------------------------------------------------------------------------------+"
+ )
+ logger.log(
+ Level.INFO,
+ " | Events: |"
+ )
+ logger.log(
+ Level.INFO,
+ " +-------------------------------------------------------------------------------------+"
+ )
+ for (command in commands) {
+ if (commandName == command.name) {
+ System.out.printf(" | * %-82s|\n", command.getAttribute())
+ }
+ }
+ logger.log(
+ Level.INFO,
+ " +-------------------------------------------------------------------------------------+"
+ )
+ }
+
+ private fun showCommand(clusterName: String, command: Command) {
+ logger.log(Level.INFO, "Usage:")
+ var arguments: String? = command.name
+ var description = ""
+ val argumentsCount = command.getArgumentsCount()
+ for (i in 0 until argumentsCount) {
+ var arg = ""
+ val isOptional = command.getArgumentIsOptional(i)
+ if (isOptional) {
+ arg += "[--"
+ }
+ arg += command.getArgumentName(i)
+ if (isOptional) {
+ arg += "]"
+ }
+ arguments += " "
+ arguments += arg
+ val argDescription = command.getArgumentDescription(i)
+ if (argDescription != null) {
+ description += "\n"
+ description += arg
+ description += ":\n "
+ description += argDescription
+ description += "\n"
+ }
+ }
+ System.out.format(" %s %s\n", clusterName, arguments)
+ val helpText = command.helpText
+ if (helpText != null) {
+ System.out.format("\n%s\n", helpText)
+ }
+ if (!description.isEmpty()) {
+ logger.log(Level.INFO, description)
+ }
+ }
+
+ companion object {
+ private val logger = Logger.getLogger(CommandManager::class.java.name)
+ }
+}
diff --git a/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/CredentialsIssuer.kt b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/CredentialsIssuer.kt
new file mode 100644
index 00000000000000..a0a384860af93c
--- /dev/null
+++ b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/CredentialsIssuer.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2023 Project CHIP Authors
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.matter.controller.commands.common
+
+/**
+ * Credentials Issuer which contains all credential information of the issuer of the command, such
+ * as operational credentials for a given fabric, the DAC verifier of the commissioner, etc ..
+ */
+class CredentialsIssuer
diff --git a/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/FutureResult.kt b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/FutureResult.kt
new file mode 100644
index 00000000000000..5fe4b5240dce3d
--- /dev/null
+++ b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/FutureResult.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2023 Project CHIP Authors
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.matter.controller.commands.common
+
+import java.util.concurrent.TimeoutException
+import java.util.logging.Level
+import java.util.logging.Logger
+
+/**
+ * Implements the future result that encapculates the optional realResult, application would wait
+ * for realResult set by other thread wben receiving data from the other end. If the expected
+ * duration elapsed without receiving the expected realResult, the runtime exception would be
+ * raised.
+ */
+class FutureResult {
+ private var realResult: RealResult? = null
+ private val lock = Object()
+ var timeoutMs: Long = 0
+
+ fun setRealResult(realResult: RealResult) {
+ synchronized(lock) {
+ if (this.realResult != null) {
+ throw TimeoutException("Error, real result has been set!")
+ }
+ this.realResult = realResult
+ lock.notifyAll()
+ }
+ }
+
+ fun waitResult() {
+ val start = System.currentTimeMillis()
+ synchronized(lock) {
+ while (realResult == null) {
+ val remainingTime = timeoutMs - (System.currentTimeMillis() - start)
+ if (remainingTime <= 0) {
+ throw TimeoutException("Timeout!")
+ }
+
+ try {
+ lock.wait(remainingTime)
+ } catch (e: InterruptedException) {
+ logger.log(Level.INFO, "Wait Result failed with exception: " + e.message)
+ }
+ }
+
+ val errorResult = realResult as? RealResult.Error
+ if (errorResult != null) {
+ logger.log(Level.INFO, "Error: ${errorResult.error}")
+ throw TimeoutException("Received failure test result")
+ }
+ }
+ }
+
+ fun clear() {
+ synchronized(lock) { realResult = null }
+ }
+
+ companion object {
+ private val logger = Logger.getLogger(FutureResult::class.java.name)
+ }
+}
diff --git a/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/IPAddress.kt b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/IPAddress.kt
new file mode 100644
index 00000000000000..c28ce9c5d5a055
--- /dev/null
+++ b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/IPAddress.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2023 Project CHIP Authors
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.matter.controller.commands.common
+
+import java.net.InetAddress
+
+class IPAddress(var address: InetAddress) {
+ override fun toString(): String {
+ return address.toString()
+ }
+}
diff --git a/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/MatterCommand.kt b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/MatterCommand.kt
new file mode 100644
index 00000000000000..6ca0e223ee921c
--- /dev/null
+++ b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/MatterCommand.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2023 Project CHIP Authors
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.matter.controller.commands.common
+
+import java.util.concurrent.atomic.AtomicBoolean
+import java.util.concurrent.atomic.AtomicLong
+import matter.controller.MatterController
+
+abstract class MatterCommand(
+ private val matterController: MatterController,
+ private val credIssuerCmds: CredentialsIssuer?,
+ commandName: String,
+ helpText: String? = null
+) : Command(commandName, helpText) {
+ private val commissionerName = StringBuffer()
+ private val paaTrustStorePath = StringBuffer()
+ private val cdTrustStorePath = StringBuffer()
+ private val commissionerNodeId: AtomicLong = AtomicLong()
+ private val useMaxSizedCerts: AtomicBoolean = AtomicBoolean()
+ private val onlyAllowTrustedCdKeys: AtomicBoolean = AtomicBoolean()
+ private val futureResult = FutureResult()
+
+ init {
+ addArgument(
+ "paa-trust-store-path",
+ paaTrustStorePath,
+ "Path to directory holding PAA certificate information. Can be absolute or relative to the current working " +
+ "directory.",
+ true
+ )
+ addArgument(
+ "cd-trust-store-path",
+ cdTrustStorePath,
+ "Path to directory holding CD certificate information. Can be absolute or relative to the current working " +
+ "directory.",
+ true
+ )
+ addArgument(
+ "commissioner-name",
+ commissionerName,
+ "Name of fabric to use. Valid values are \"alpha\", \"beta\", \"gamma\", and integers greater than or equal to " +
+ "4. The default if not specified is \"alpha\".",
+ true
+ )
+ addArgument(
+ "commissioner-nodeid",
+ 0,
+ Long.MAX_VALUE,
+ commissionerNodeId,
+ "The node id to use for kotlin-matter-controller. If not provided, kTestControllerNodeId (112233, 0x1B669) will be used.",
+ true
+ )
+ addArgument(
+ "use-max-sized-certs",
+ useMaxSizedCerts,
+ "Maximize the size of operational certificates. If not provided or 0 (\"false\"), normally sized operational " +
+ "certificates are generated.",
+ true
+ )
+ addArgument(
+ "only-allow-trusted-cd-keys",
+ onlyAllowTrustedCdKeys,
+ "Only allow trusted CD verifying keys (disallow test keys). If not provided or 0 (\"false\"), untrusted CD " +
+ "verifying keys are allowed. If 1 (\"true\"), test keys are disallowed.",
+ true
+ )
+ }
+
+ // This method returns the commissioner instance to be used for running the command.
+ fun currentCommissioner(): MatterController {
+ return matterController
+ }
+
+ /////////// Command Interface /////////
+ @Throws(Exception::class)
+ override fun run() {
+ runCommand()
+ }
+
+ protected abstract fun runCommand()
+
+ fun setSuccess() {
+ futureResult.setRealResult(RealResult.success())
+ }
+
+ fun setFailure(error: String?) {
+ futureResult.setRealResult(RealResult.error(error))
+ }
+
+ fun waitCompleteMs(timeoutMs: Long) {
+ futureResult.timeoutMs = timeoutMs
+ futureResult.waitResult()
+ }
+
+ fun clear() {
+ futureResult.clear()
+ }
+}
diff --git a/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/RealResult.kt b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/RealResult.kt
new file mode 100644
index 00000000000000..50debc33f0d014
--- /dev/null
+++ b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/common/RealResult.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2023 Project CHIP Authors
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.matter.controller.commands.common
+
+/**
+ * Represents a result that can either indicate success or failure with an associated error message.
+ *
+ * In the context of RealResult, success is represented by [Success] and failure by [Error]. When
+ * there is an error, an error message explains the reason for the failure.
+ */
+sealed class RealResult {
+ data class Error(val error: String) : RealResult()
+
+ object Success : RealResult()
+
+ companion object {
+ fun success(): RealResult {
+ return Success
+ }
+
+ fun error(error: String?): RealResult {
+ return error?.let { Error(it) } ?: Success
+ }
+ }
+}
diff --git a/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/DiscoveryFilterType.kt b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/DiscoveryFilterType.kt
new file mode 100644
index 00000000000000..eb254ae5ec638d
--- /dev/null
+++ b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/DiscoveryFilterType.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2023 Project CHIP Authors
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.matter.controller.commands.pairing
+
+enum class DiscoveryFilterType {
+ NONE,
+ SHORT_DISCRIMINATOR,
+ LONG_DISCRIMINATOR,
+ VENDOR_ID,
+ DEVICE_TYPE,
+ COMMISSIONING_MODE,
+ INSTANCE_NAME,
+ COMMISSIONER,
+ COMPRESSED_FABRIC_ID
+}
diff --git a/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongCommand.kt b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongCommand.kt
new file mode 100644
index 00000000000000..594d30d5fa3c8c
--- /dev/null
+++ b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongCommand.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2023 Project CHIP Authors
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.matter.controller.commands.pairing
+
+import com.matter.controller.commands.common.CredentialsIssuer
+import matter.controller.MatterController
+
+private const val MATTER_PORT = 5540
+
+class PairOnNetworkLongCommand(controller: MatterController, credsIssue: CredentialsIssuer?) :
+ PairingCommand(
+ controller,
+ "onnetwork-long",
+ credsIssue,
+ PairingModeType.ON_NETWORK,
+ PairingNetworkType.NONE,
+ DiscoveryFilterType.LONG_DISCRIMINATOR
+ ) {
+ override fun runCommand() {
+ currentCommissioner()
+ .pairDevice(
+ getNodeId(),
+ getRemoteAddr().address.hostAddress,
+ MATTER_PORT,
+ getDiscriminator(),
+ getSetupPINCode(),
+ )
+ currentCommissioner().setCompletionListener(this)
+ waitCompleteMs(getTimeoutMillis())
+ }
+}
diff --git a/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImInvokeCommand.kt b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImInvokeCommand.kt
new file mode 100644
index 00000000000000..313e67419927a0
--- /dev/null
+++ b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImInvokeCommand.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2023 Project CHIP Authors
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.matter.controller.commands.pairing
+
+import chip.tlv.AnonymousTag
+import chip.tlv.ContextSpecificTag
+import chip.tlv.TlvWriter
+import com.matter.controller.commands.common.CredentialsIssuer
+import java.time.Duration
+import java.util.logging.Level
+import java.util.logging.Logger
+import kotlinx.coroutines.runBlocking
+import matter.controller.InvokeRequest
+import matter.controller.InvokeResponse
+import matter.controller.MatterController
+import matter.controller.model.CommandPath
+
+class PairOnNetworkLongImInvokeCommand(
+ controller: MatterController,
+ credsIssue: CredentialsIssuer?
+) :
+ PairingCommand(
+ controller,
+ "onnetwork-long-im-invoke",
+ credsIssue,
+ PairingModeType.ON_NETWORK,
+ PairingNetworkType.NONE,
+ DiscoveryFilterType.LONG_DISCRIMINATOR
+ ) {
+ override fun runCommand() {
+ val IdentifyTime: UShort = 1u
+ val tlvWriter1 = TlvWriter()
+ tlvWriter1.startStructure(AnonymousTag)
+ tlvWriter1.put(ContextSpecificTag(0), IdentifyTime)
+ tlvWriter1.endStructure()
+
+ val element1: InvokeRequest =
+ InvokeRequest(
+ CommandPath(endpointId = 0u, clusterId = CLUSTER_ID_IDENTIFY, commandId = IDENTIFY_COMMAND),
+ tlvPayload = tlvWriter1.getEncoded(),
+ timedRequest = Duration.ZERO
+ )
+
+ currentCommissioner()
+ .pairDevice(
+ getNodeId(),
+ getRemoteAddr().address.hostAddress,
+ MATTER_PORT,
+ getDiscriminator(),
+ getSetupPINCode(),
+ )
+ currentCommissioner().setCompletionListener(this)
+ waitCompleteMs(getTimeoutMillis())
+
+ runBlocking {
+ try {
+ val response: InvokeResponse = currentCommissioner().invoke(element1)
+ logger.log(Level.INFO, "Invoke command succeeded")
+ if (response.payload.isNotEmpty()) {
+ // TODO:Handle TLV data response
+ }
+ } catch (ex: Exception) {
+ setFailure("invoke failure: ${ex.message}")
+ } finally {
+ clear()
+ }
+ }
+
+ setSuccess()
+ }
+
+ companion object {
+ private val logger = Logger.getLogger(PairOnNetworkLongImInvokeCommand::class.java.name)
+
+ private const val MATTER_PORT = 5540
+ private const val CLUSTER_ID_IDENTIFY = 0x0003u
+ private const val IDENTIFY_COMMAND = 0u
+ private const val CLUSTER_ID_TEST = 0xFFF1FC05u
+ private const val TEST_ADD_ARGUMENT_COMMAND = 0X04u
+ }
+}
diff --git a/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImReadCommand.kt b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImReadCommand.kt
new file mode 100644
index 00000000000000..15cf62ed504067
--- /dev/null
+++ b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImReadCommand.kt
@@ -0,0 +1,109 @@
+package com.matter.controller.commands.pairing
+
+import com.matter.controller.commands.common.CredentialsIssuer
+import java.util.logging.Level
+import java.util.logging.Logger
+import kotlinx.coroutines.runBlocking
+import matter.controller.MatterController
+import matter.controller.ReadData
+import matter.controller.ReadRequest
+import matter.controller.ReadResponse
+import matter.controller.model.AttributePath
+import matter.controller.model.EventPath
+
+class PairOnNetworkLongImReadCommand(controller: MatterController, credsIssue: CredentialsIssuer?) :
+ PairingCommand(
+ controller,
+ "onnetwork-long-im-read",
+ credsIssue,
+ PairingModeType.ON_NETWORK,
+ PairingNetworkType.NONE,
+ DiscoveryFilterType.LONG_DISCRIMINATOR
+ ) {
+ override fun runCommand() {
+ val attributePaths =
+ listOf(
+ AttributePath(
+ endpointId = DEFAULT_ENDPOINT,
+ clusterId = CLUSTER_ID_BASIC,
+ attributeId = ATTR_ID_LOCAL_CONFIG_DISABLED,
+ ),
+ AttributePath(
+ endpointId = DEFAULT_ENDPOINT,
+ clusterId = CLUSTER_ID_BASIC,
+ attributeId = GLOBAL_ATTRIBUTE_LIST,
+ )
+ )
+
+ val eventPaths =
+ listOf(
+ EventPath(
+ endpointId = WILDCARD_ENDPOINT_ID,
+ clusterId = WILDCARD_CLUSTER_ID,
+ eventId = WILDCARD_EVENT_ID
+ )
+ )
+
+ val readRequest: ReadRequest = ReadRequest(eventPaths, attributePaths)
+
+ currentCommissioner()
+ .pairDevice(
+ getNodeId(),
+ getRemoteAddr().address.hostAddress,
+ MATTER_PORT,
+ getDiscriminator(),
+ getSetupPINCode(),
+ )
+ currentCommissioner().setCompletionListener(this)
+ waitCompleteMs(getTimeoutMillis())
+
+ runBlocking {
+ try {
+ val response: ReadResponse = currentCommissioner().read(readRequest)
+ logger.log(Level.INFO, "Read command succeeded")
+ validateResponse(response)
+ } catch (ex: Exception) {
+ logger.log(Level.WARNING, "General read failure occurred with error ${ex.message}")
+ setFailure("read failure")
+ } finally {
+ clear()
+ }
+ }
+
+ setSuccess()
+ }
+
+ private fun findAttributeById(successes: List, id: UInt): ReadData.Attribute? {
+ return successes.filterIsInstance().find { it.path.attributeId == id }
+ }
+
+ private fun findEventById(successes: List, id: UInt): ReadData.Event? {
+ return successes.filterIsInstance().find { it.path.eventId == id }
+ }
+
+ private fun validateResponse(response: ReadResponse) {
+ require(response.successes.isNotEmpty()) { "Unexpected: response.successes is empty" }
+ require(response.failures.isEmpty()) { "Unexpected: response.failures is not empty" }
+
+ val localConfigDisabledAttribute =
+ findAttributeById(response.successes, ATTR_ID_LOCAL_CONFIG_DISABLED)
+ requireNotNull(localConfigDisabledAttribute) { "No local config disabled attribute found." }
+
+ // TODO: Add more validation rules as needed
+ }
+
+ companion object {
+ private val logger = Logger.getLogger(PairOnNetworkLongImReadCommand::class.java.name)
+
+ private const val MATTER_PORT = 5540
+ private const val DEFAULT_ENDPOINT: UShort = 0u
+ private const val WILDCARD_ENDPOINT_ID: UShort = 0xffffu
+ private const val WILDCARD_CLUSTER_ID: UInt = 0xffffffffu
+ private const val WILDCARD_ATTRIBUTE_ID: UInt = 0xffffffffu
+ private const val WILDCARD_EVENT_ID: UInt = 0xffffffffu
+ private const val CLUSTER_ID_BASIC: UInt = 0x0028u
+ private const val ATTR_ID_LOCAL_CONFIG_DISABLED: UInt = 16u
+ private const val EVENT_ID_START_UP: UInt = 0u
+ private const val GLOBAL_ATTRIBUTE_LIST: UInt = 65531u
+ }
+}
diff --git a/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImSubscribeCommand.kt b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImSubscribeCommand.kt
new file mode 100644
index 00000000000000..c2ea79683ec980
--- /dev/null
+++ b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImSubscribeCommand.kt
@@ -0,0 +1,113 @@
+package com.matter.controller.commands.pairing
+
+import com.matter.controller.commands.common.CredentialsIssuer
+import java.util.logging.Level
+import java.util.logging.Logger
+import kotlinx.coroutines.flow.takeWhile
+import kotlinx.coroutines.runBlocking
+import matter.controller.MatterController
+import matter.controller.SubscribeRequest
+import matter.controller.SubscriptionState
+import matter.controller.model.AttributePath
+import matter.controller.model.EventPath
+
+class PairOnNetworkLongImSubscribeCommand(
+ controller: MatterController,
+ credsIssue: CredentialsIssuer?
+) :
+ PairingCommand(
+ controller,
+ "onnetwork-long-im-subscribe",
+ credsIssue,
+ PairingModeType.ON_NETWORK,
+ PairingNetworkType.NONE,
+ DiscoveryFilterType.LONG_DISCRIMINATOR
+ ) {
+ override fun runCommand() {
+ val attributePaths =
+ listOf(
+ AttributePath(
+ endpointId = WILDCARD_ENDPOINT_ID,
+ clusterId = WILDCARD_CLUSTER_ID,
+ attributeId = WILDCARD_EVENT_ID,
+ )
+ )
+
+ val eventPaths =
+ listOf(
+ EventPath(
+ endpointId = WILDCARD_ENDPOINT_ID,
+ clusterId = WILDCARD_CLUSTER_ID,
+ eventId = WILDCARD_EVENT_ID
+ )
+ )
+
+ val subscribeRequest: SubscribeRequest = SubscribeRequest(eventPaths, attributePaths)
+
+ currentCommissioner()
+ .pairDevice(
+ getNodeId(),
+ getRemoteAddr().address.hostAddress,
+ MATTER_PORT,
+ getDiscriminator(),
+ getSetupPINCode(),
+ )
+ currentCommissioner().setCompletionListener(this)
+ waitCompleteMs(getTimeoutMillis())
+
+ runBlocking {
+ try {
+ startSubscription(subscribeRequest)
+ } catch (ex: Exception) {
+ logger.log(Level.WARNING, "General subscribe failure occurred with error ${ex.message}")
+ setFailure("subscribe failure")
+ } finally {
+ clear()
+ }
+ }
+
+ setSuccess()
+ }
+
+ private suspend fun startSubscription(request: SubscribeRequest) {
+ logger.log(Level.INFO, "Starting subscription")
+
+ currentCommissioner()
+ .subscribe(request)
+ .takeWhile { subscriptionState ->
+ // Keep collecting as long as it's not SubscriptionEstablished
+ subscriptionState !is SubscriptionState.SubscriptionEstablished
+ }
+ .collect { subscriptionState ->
+ when (subscriptionState) {
+ is SubscriptionState.NodeStateUpdate -> {
+ logger.log(Level.INFO, "Received NodeStateUpdate: ${subscriptionState.updateState}")
+
+ // TODO: Add more validation rules as needed
+ }
+ is SubscriptionState.SubscriptionErrorNotification -> {
+ logger.log(
+ Level.WARNING,
+ "Received SubscriptionErrorNotification with terminationCause: ${subscriptionState.terminationCause}"
+ )
+ }
+ is SubscriptionState.SubscriptionEstablished -> {
+ logger.log(Level.INFO, "Subscription is established")
+ }
+ else -> {
+ logger.log(Level.SEVERE, "Unexpected subscription state: $subscriptionState")
+ }
+ }
+ }
+ }
+
+ companion object {
+ private val logger = Logger.getLogger(PairOnNetworkLongImSubscribeCommand::class.java.name)
+
+ private const val MATTER_PORT = 5540
+ private const val WILDCARD_ENDPOINT_ID: UShort = 0xffffu
+ private const val WILDCARD_CLUSTER_ID: UInt = 0xffffffffu
+ private const val WILDCARD_ATTRIBUTE_ID: UInt = 0xffffffffu
+ private const val WILDCARD_EVENT_ID: UInt = 0xffffffffu
+ }
+}
diff --git a/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImWriteCommand.kt b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImWriteCommand.kt
new file mode 100644
index 00000000000000..376e132194c889
--- /dev/null
+++ b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImWriteCommand.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2023 Project CHIP Authors
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.matter.controller.commands.pairing
+
+import chip.tlv.AnonymousTag
+import chip.tlv.TlvWriter
+import com.matter.controller.commands.common.CredentialsIssuer
+import java.time.Duration
+import java.util.logging.Level
+import java.util.logging.Logger
+import kotlinx.coroutines.runBlocking
+import matter.controller.MatterController
+import matter.controller.WriteRequest
+import matter.controller.WriteRequests
+import matter.controller.WriteResponse
+import matter.controller.model.AttributePath
+
+class PairOnNetworkLongImWriteCommand(
+ controller: MatterController,
+ credsIssue: CredentialsIssuer?
+) :
+ PairingCommand(
+ controller,
+ "onnetwork-long-im-write",
+ credsIssue,
+ PairingModeType.ON_NETWORK,
+ PairingNetworkType.NONE,
+ DiscoveryFilterType.LONG_DISCRIMINATOR
+ ) {
+ override fun runCommand() {
+ val tlvWriter1 = TlvWriter()
+ tlvWriter1.put(AnonymousTag, true)
+ val writeRequests: WriteRequests =
+ WriteRequests(
+ requests =
+ listOf(
+ WriteRequest(
+ attributePath =
+ AttributePath(
+ endpointId = 0u,
+ clusterId = CLUSTER_ID_BASIC,
+ attributeId = ATTR_ID_LOCAL_CONFIG_DISABLED
+ ),
+ tlvPayload = tlvWriter1.getEncoded()
+ )
+ ),
+ timedRequest = Duration.ZERO
+ )
+
+ currentCommissioner()
+ .pairDevice(
+ getNodeId(),
+ getRemoteAddr().address.hostAddress,
+ MATTER_PORT,
+ getDiscriminator(),
+ getSetupPINCode(),
+ )
+ currentCommissioner().setCompletionListener(this)
+ waitCompleteMs(getTimeoutMillis())
+
+ runBlocking {
+ try {
+ val response: WriteResponse = currentCommissioner().write(writeRequests)
+
+ if (response is WriteResponse.Success) {
+ logger.log(Level.INFO, "Write command succeeded")
+ } else if (response is WriteResponse.PartialWriteFailure) {
+ logger.log(
+ Level.WARNING,
+ "Partial write failure occurred with ${response.failures.size} errors"
+ )
+
+ for ((index, error) in response.failures.withIndex()) {
+ logger.log(Level.WARNING, "Error ${index + 1}:")
+ logger.log(Level.WARNING, "Attribute Path: ${error.attributePath}")
+ logger.log(Level.WARNING, "Exception Message: ${error.ex.message}")
+ }
+
+ setFailure("invoke failure")
+ }
+ } catch (ex: Exception) {
+ setFailure("invoke failure: ${ex.message}")
+ } catch (ex: Exception) {
+ logger.log(Level.WARNING, "General write failure occurred with error ${ex.message}")
+ setFailure("invoke failure")
+ } finally {
+ clear()
+ }
+ }
+
+ setSuccess()
+ }
+
+ companion object {
+ private val logger = Logger.getLogger(PairOnNetworkLongImWriteCommand::class.java.name)
+
+ private const val MATTER_PORT = 5540
+ private const val CLUSTER_ID_BASIC = 0x0028u
+ private const val ATTR_ID_LOCAL_CONFIG_DISABLED = 16u
+ }
+}
diff --git a/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairingCommand.kt b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairingCommand.kt
new file mode 100644
index 00000000000000..96b856568ccbc5
--- /dev/null
+++ b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairingCommand.kt
@@ -0,0 +1,219 @@
+/*
+ * Copyright (c) 2023 Project CHIP Authors
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.matter.controller.commands.pairing
+
+import com.matter.controller.commands.common.CredentialsIssuer
+import com.matter.controller.commands.common.IPAddress
+import com.matter.controller.commands.common.MatterCommand
+import java.net.InetAddress
+import java.util.concurrent.atomic.AtomicBoolean
+import java.util.concurrent.atomic.AtomicInteger
+import java.util.concurrent.atomic.AtomicLong
+import java.util.logging.Level
+import java.util.logging.Logger
+import matter.controller.MatterController
+
+abstract class PairingCommand(
+ controller: MatterController,
+ commandName: String,
+ credsIssuer: CredentialsIssuer?,
+ private val pairingMode: PairingModeType = PairingModeType.NONE,
+ private val networkType: PairingNetworkType = PairingNetworkType.NONE,
+ private val filterType: DiscoveryFilterType = DiscoveryFilterType.NONE
+) : MatterCommand(controller, credsIssuer, commandName), MatterController.CompletionListener {
+ private val remoteAddr: IPAddress = IPAddress(InetAddress.getByName("::1"))
+ private val nodeId = AtomicLong()
+ private val discoveryFilterCode = AtomicLong()
+ private val timeoutMillis = AtomicLong()
+ private val discoverOnce = AtomicBoolean()
+ private val useOnlyOnNetworkDiscovery = AtomicBoolean()
+ private val remotePort = AtomicInteger()
+ private val discriminator = AtomicInteger()
+ private val setupPINCode = AtomicLong()
+ private val operationalDataset = StringBuffer()
+ private val ssid = StringBuffer()
+ private val password = StringBuffer()
+ private val onboardingPayload = StringBuffer()
+ private val discoveryFilterInstanceName = StringBuffer()
+
+ init {
+ addArgument("node-id", 0, Long.MAX_VALUE, nodeId, null, false)
+
+ when (networkType) {
+ PairingNetworkType.NONE -> {}
+ PairingNetworkType.WIFI -> {
+ addArgument("ssid", ssid, null, false)
+ addArgument("password", password, null, false)
+ }
+ PairingNetworkType.THREAD ->
+ addArgument("operationalDataset", operationalDataset, null, false)
+ }
+
+ when (pairingMode) {
+ PairingModeType.NONE -> {}
+ PairingModeType.CODE,
+ PairingModeType.CODE_PASE_ONLY -> {
+ addArgument("payload", onboardingPayload, null, false)
+ addArgument("discover-once", discoverOnce, null, true)
+ addArgument("use-only-onnetwork-discovery", useOnlyOnNetworkDiscovery, null, true)
+ }
+ PairingModeType.ADDRESS_PASE_ONLY -> {
+ addArgument("setup-pin-code", 0, 134217727, setupPINCode, null, false)
+ addArgument("device-remote-ip", remoteAddr, false)
+ addArgument("device-remote-port", 0.toShort(), Short.MAX_VALUE, remotePort, null, false)
+ }
+ PairingModeType.BLE -> {
+ addArgument("setup-pin-code", 0, 134217727, setupPINCode, null, false)
+ addArgument("discriminator", 0.toShort(), 4096.toShort(), discriminator, null, false)
+ }
+ PairingModeType.ON_NETWORK ->
+ addArgument("setup-pin-code", 0, 134217727, setupPINCode, null, false)
+ PairingModeType.SOFT_AP -> {
+ addArgument("setup-pin-code", 0, 134217727, setupPINCode, null, false)
+ addArgument("discriminator", 0.toShort(), 4096.toShort(), discriminator, null, false)
+ addArgument("device-remote-ip", remoteAddr, false)
+ addArgument("device-remote-port", 0.toShort(), Short.MAX_VALUE, remotePort, null, false)
+ }
+ PairingModeType.ALREADY_DISCOVERED -> {
+ addArgument("setup-pin-code", 0, 134217727, setupPINCode, null, false)
+ addArgument("device-remote-ip", remoteAddr, false)
+ addArgument("device-remote-port", 0.toShort(), Short.MAX_VALUE, remotePort, null, false)
+ }
+ }
+
+ when (filterType) {
+ DiscoveryFilterType.NONE -> {}
+ DiscoveryFilterType.SHORT_DISCRIMINATOR,
+ DiscoveryFilterType.LONG_DISCRIMINATOR ->
+ addArgument("discriminator", 0.toShort(), 4096.toShort(), discriminator, null, false)
+ DiscoveryFilterType.VENDOR_ID ->
+ addArgument("vendor-id", 1.toShort(), Short.MAX_VALUE, discoveryFilterCode, null, false)
+ DiscoveryFilterType.COMPRESSED_FABRIC_ID ->
+ addArgument("fabric-id", 0L, Long.MAX_VALUE, discoveryFilterCode, null, false)
+ DiscoveryFilterType.COMMISSIONING_MODE,
+ DiscoveryFilterType.COMMISSIONER -> {}
+ DiscoveryFilterType.DEVICE_TYPE ->
+ addArgument("device-type", 0.toShort(), Short.MAX_VALUE, discoveryFilterCode, null, false)
+ DiscoveryFilterType.INSTANCE_NAME ->
+ addArgument("name", discoveryFilterInstanceName, null, false)
+ }
+
+ addArgument("timeout", 0L, Long.MAX_VALUE, timeoutMillis, null, false)
+ }
+
+ override fun onConnectDeviceComplete() {
+ logger.log(Level.INFO, "onConnectDeviceComplete")
+ }
+
+ override fun onStatusUpdate(status: Int) {
+ logger.log(Level.INFO, "onStatusUpdate with status: $status")
+ }
+
+ override fun onPairingComplete(errorCode: Int) {
+ logger.log(Level.INFO, "onPairingComplete with error code: $errorCode")
+ if (errorCode != 0) {
+ setFailure("onPairingComplete failure")
+ }
+ }
+
+ override fun onPairingDeleted(errorCode: Int) {
+ logger.log(Level.INFO, "onPairingDeleted with error code: $errorCode")
+ }
+
+ override fun onCommissioningComplete(nodeId: Long, errorCode: Int) {
+ logger.log(Level.INFO, "onCommissioningComplete with error code: $errorCode")
+ if (errorCode == 0) {
+ setSuccess()
+ } else {
+ setFailure("onCommissioningComplete failure")
+ }
+ }
+
+ override fun onReadCommissioningInfo(
+ vendorId: Int,
+ productId: Int,
+ wifiEndpointId: Int,
+ threadEndpointId: Int
+ ) {
+ logger.log(Level.INFO, "onReadCommissioningInfo")
+ }
+
+ override fun onCommissioningStatusUpdate(nodeId: Long, stage: String?, errorCode: Int) {
+ logger.log(Level.INFO, "onCommissioningStatusUpdate")
+ }
+
+ override fun onNotifyChipConnectionClosed() {
+ logger.log(Level.INFO, "onNotifyChipConnectionClosed")
+ }
+
+ override fun onError(error: Throwable) {
+ setFailure(error.toString())
+ logger.log(Level.INFO, "onError with error: $error")
+ }
+
+ override fun onOpCSRGenerationComplete(csr: ByteArray) {
+ logger.log(Level.INFO, "onOpCSRGenerationComplete")
+ for (i in csr.indices) {
+ print(csr[i].toString() + " ")
+ }
+ }
+
+ fun getNodeId(): Long {
+ return nodeId.get()
+ }
+
+ fun getRemoteAddr(): IPAddress {
+ return remoteAddr
+ }
+
+ fun getRemotePort(): Int {
+ return remotePort.get()
+ }
+
+ fun getSetupPINCode(): Long {
+ return setupPINCode.get()
+ }
+
+ fun getDiscriminator(): Int {
+ return discriminator.get()
+ }
+
+ fun getTimeoutMillis(): Long {
+ return timeoutMillis.get()
+ }
+
+ fun getOnboardingPayload(): String {
+ return onboardingPayload.toString()
+ }
+
+ private fun String.hexToByteArray(): ByteArray {
+ return chunked(2).map { byteStr -> byteStr.toUByte(16).toByte() }.toByteArray()
+ }
+
+ fun getDiscoverOnce(): Boolean {
+ return discoverOnce.get()
+ }
+
+ fun getUseOnlyOnNetworkDiscovery(): Boolean {
+ return useOnlyOnNetworkDiscovery.get()
+ }
+
+ companion object {
+ private val logger = Logger.getLogger(PairingCommand::class.java.name)
+ }
+}
diff --git a/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairingModeType.kt b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairingModeType.kt
new file mode 100644
index 00000000000000..590688da9feda5
--- /dev/null
+++ b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairingModeType.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2023 Project CHIP Authors
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.matter.controller.commands.pairing
+
+enum class PairingModeType {
+ NONE,
+ CODE,
+ CODE_PASE_ONLY,
+ ADDRESS_PASE_ONLY,
+ BLE,
+ SOFT_AP,
+ ALREADY_DISCOVERED,
+ ON_NETWORK
+}
diff --git a/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairingNetworkType.kt b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairingNetworkType.kt
new file mode 100644
index 00000000000000..4cde1fc97642d6
--- /dev/null
+++ b/examples/kotlin-matter-controller/java/src/com/matter/controller/commands/pairing/PairingNetworkType.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2023 Project CHIP Authors
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.matter.controller.commands.pairing
+
+enum class PairingNetworkType {
+ NONE,
+ WIFI,
+ THREAD
+}
diff --git a/examples/kotlin-matter-controller/third_party/connectedhomeip b/examples/kotlin-matter-controller/third_party/connectedhomeip
new file mode 120000
index 00000000000000..1b20c9fb816b63
--- /dev/null
+++ b/examples/kotlin-matter-controller/third_party/connectedhomeip
@@ -0,0 +1 @@
+../../../
\ No newline at end of file
diff --git a/scripts/build/build/targets.py b/scripts/build/build/targets.py
index c1b0fad61be8ff..17807371745ea6 100755
--- a/scripts/build/build/targets.py
+++ b/scripts/build/build/targets.py
@@ -111,6 +111,8 @@ def BuildHostTarget():
TargetPart('thermostat', app=HostApp.THERMOSTAT),
TargetPart('java-matter-controller',
app=HostApp.JAVA_MATTER_CONTROLLER),
+ TargetPart('kotlin-matter-controller',
+ app=HostApp.KOTLIN_MATTER_CONTROLLER),
TargetPart('minmdns', app=HostApp.MIN_MDNS),
TargetPart('light', app=HostApp.LIGHT),
TargetPart('lock', app=HostApp.LOCK),
@@ -332,6 +334,8 @@ def BuildAndroidTarget():
TargetPart('tv-casting-app', app=AndroidApp.TV_CASTING_APP),
TargetPart('java-matter-controller',
app=AndroidApp.JAVA_MATTER_CONTROLLER),
+ TargetPart('kotlin-matter-controller',
+ app=AndroidApp.KOTLIN_MATTER_CONTROLLER),
TargetPart('virtual-device-app',
app=AndroidApp.VIRTUAL_DEVICE_APP),
])
diff --git a/scripts/build/builders/android.py b/scripts/build/builders/android.py
index a5b86e7ccd1388..14802fe5e83e80 100644
--- a/scripts/build/builders/android.py
+++ b/scripts/build/builders/android.py
@@ -71,6 +71,7 @@ class AndroidApp(Enum):
TV_SERVER = auto()
TV_CASTING_APP = auto()
JAVA_MATTER_CONTROLLER = auto()
+ KOTLIN_MATTER_CONTROLLER = auto()
VIRTUAL_DEVICE_APP = auto()
def AppName(self):
diff --git a/scripts/build/builders/host.py b/scripts/build/builders/host.py
index 01a9e771136032..fcc159894bda29 100644
--- a/scripts/build/builders/host.py
+++ b/scripts/build/builders/host.py
@@ -67,6 +67,7 @@ class HostApp(Enum):
TV_CASTING = auto()
BRIDGE = auto()
JAVA_MATTER_CONTROLLER = auto()
+ KOTLIN_MATTER_CONTROLLER = auto()
CONTACT_SENSOR = auto()
DISHWASHER = auto()
REFRIGERATOR = auto()
@@ -113,6 +114,8 @@ def ExamplePath(self):
return 'bridge-app/linux'
elif self == HostApp.JAVA_MATTER_CONTROLLER:
return 'java-matter-controller'
+ elif self == HostApp.KOTLIN_MATTER_CONTROLLER:
+ return 'kotlin-matter-controller'
elif self == HostApp.CONTACT_SENSOR:
return 'contact-sensor-app/linux'
elif self == HostApp.DISHWASHER:
@@ -197,6 +200,9 @@ def OutputNames(self):
elif self == HostApp.JAVA_MATTER_CONTROLLER:
yield 'java-matter-controller'
yield 'java-matter-controller.map'
+ elif self == HostApp.KOTLIN_MATTER_CONTROLLER:
+ yield 'kotlin-matter-controller'
+ yield 'kotlin-matter-controller.map'
elif self == HostApp.CONTACT_SENSOR:
yield 'contact-sensor-app'
yield 'contact-sensor-app.map'
@@ -451,6 +457,15 @@ def generate(self):
],
title="Copying Manifest.txt to " + self.output_dir,
)
+ if exampleName == "kotlin-matter-controller":
+ self._Execute(
+ [
+ "cp",
+ os.path.join(self.root, "Manifest.txt"),
+ self.output_dir,
+ ],
+ title="Copying Manifest.txt to " + self.output_dir,
+ )
if self.app == HostApp.TESTS and self.use_coverage:
self.coverage_dir = os.path.join(self.output_dir, 'coverage')
@@ -484,6 +499,9 @@ def PostBuildCommand(self):
if self.app == HostApp.JAVA_MATTER_CONTROLLER:
self.createJavaExecutable("java-matter-controller")
+ if self.app == HostApp.KOTLIN_MATTER_CONTROLLER:
+ self.createJavaExecutable("kotlin-matter-controller")
+
def build_outputs(self):
outputs = {}
diff --git a/scripts/build/testdata/all_targets_linux_x64.txt b/scripts/build/testdata/all_targets_linux_x64.txt
index dd29ccecea337c..4c65d6c04db464 100644
--- a/scripts/build/testdata/all_targets_linux_x64.txt
+++ b/scripts/build/testdata/all_targets_linux_x64.txt
@@ -1,6 +1,6 @@
ameba-amebad-{all-clusters,all-clusters-minimal,light,light-switch,pigweed}
asr-{asr582x,asr595x,asr550x}-{all-clusters,all-clusters-minimal,lighting,light-switch,lock,bridge,temperature-measurement,thermostat,ota-requestor,dishwasher,refrigerator}[-ota][-shell][-no_logging][-factory][-rotating_id][-rio]
-android-{arm,arm64,x86,x64,androidstudio-arm,androidstudio-arm64,androidstudio-x86,androidstudio-x64}-{chip-tool,chip-test,tv-server,tv-casting-app,java-matter-controller,virtual-device-app}[-no-debug]
+android-{arm,arm64,x86,x64,androidstudio-arm,androidstudio-arm64,androidstudio-x86,androidstudio-x64}-{chip-tool,chip-test,tv-server,tv-casting-app,java-matter-controller,kotlin-matter-controller,virtual-device-app}[-no-debug]
bouffalolab-{bl602-iot-matter-v1,bl602-night-light,xt-zb6-devkit,bl706-night-light,bl706dk,bl704ldk}-light[-shell][-115200][-rpc][-cdc][-resetcnt][-rotating_device_id][-mfd][-mfdtest][-ethernet][-wifi][-thread][-fp][-memmonitor]
cc32xx-lock
ti-cc13x2x7_26x2x7-{lighting,lock,pump,pump-controller}[-mtd]
@@ -10,7 +10,7 @@ efr32-{brd4161a,brd4187c,brd4186c,brd4163a,brd4164a,brd4166a,brd4170a,brd4186a,b
esp32-{m5stack,c3devkit,devkitc,qemu}-{all-clusters,all-clusters-minimal,ota-provider,ota-requestor,shell,light,lock,bridge,temperature-measurement,ota-requestor,tests}[-rpc][-ipv6only][-tracing]
genio-lighting-app
linux-fake-tests[-mbedtls][-boringssl][-asan][-tsan][-ubsan][-libfuzzer][-ossfuzz][-coverage][-dmalloc][-clang]
-linux-{x64,arm64}-{rpc-console,all-clusters,all-clusters-minimal,chip-tool,thermostat,java-matter-controller,minmdns,light,lock,shell,ota-provider,ota-requestor,simulated-app1,simulated-app2,python-bindings,tv-app,tv-casting-app,bridge,tests,chip-cert,address-resolve-tool,contact-sensor,dishwasher,refrigerator,rvc}[-nodeps][-platform-mdns][-minmdns-verbose][-libnl][-same-event-loop][-no-interactive][-ipv6only][-no-ble][-no-wifi][-no-thread][-mbedtls][-boringssl][-asan][-tsan][-ubsan][-libfuzzer][-ossfuzz][-coverage][-dmalloc][-clang][-test][-rpc][-with-ui]
+linux-{x64,arm64}-{rpc-console,all-clusters,all-clusters-minimal,chip-tool,thermostat,java-matter-controller,kotlin-matter-controller,minmdns,light,lock,shell,ota-provider,ota-requestor,simulated-app1,simulated-app2,python-bindings,tv-app,tv-casting-app,bridge,tests,chip-cert,address-resolve-tool,contact-sensor,dishwasher,refrigerator,rvc}[-nodeps][-platform-mdns][-minmdns-verbose][-libnl][-same-event-loop][-no-interactive][-ipv6only][-no-ble][-no-wifi][-no-thread][-mbedtls][-boringssl][-asan][-tsan][-ubsan][-libfuzzer][-ossfuzz][-coverage][-dmalloc][-clang][-test][-rpc][-with-ui]
linux-x64-efr32-test-runner[-clang]
imx-{chip-tool,lighting-app,thermostat,all-clusters-app,all-clusters-minimal-app,ota-provider-app}[-release]
infineon-psoc6-{lock,light,all-clusters,all-clusters-minimal}[-ota][-updateimage]