[python] Deprecate chip-device-ctrl step: 1 Add "replhint" for most commands (#21845)

diff --git a/docs/guides/python_chip_controller_building.md b/docs/guides/python_chip_controller_building.md
index 8c9c6e5..693dacf 100644
--- a/docs/guides/python_chip_controller_building.md
+++ b/docs/guides/python_chip_controller_building.md
@@ -4,6 +4,9 @@
 into the network and to communicate with it using the Zigbee Cluster Library
 (ZCL) messages.
 
+> The chip-device-ctrl tool will be deprecated, and will be replaced by
+> chip-repl. Continue reading to see how to do the same thing with chip-repl.
+
 <hr>
 
 -   [Source files](#source)
@@ -192,6 +195,8 @@
 chip-device-ctrl > set-pairing-wifi-credential TESTSSID P455W4RD
 ```
 
+**REPL Command**: `devCtrl.SetWiFiCredentials(<ssid>, <password>)`
+
 ### Step 5: Commission the Matter accessory device over Bluetooth LE
 
 The controller uses a 12-bit value called **discriminator** to discern between
@@ -222,6 +227,9 @@
 chip-device-ctrl > connect -ble 3840 20202021 1234
 ```
 
+**REPL Command:**
+`devCtrl.ConnectBLE(<discriminator>, <setup pincode>, <temporary node id>)`
+
 You can skip the last parameter, the Node ID, in the command. If you skip it,
 the controller will assign it randomly. In that case, note down the Node ID,
 because it is required later in the configuration process.
@@ -258,6 +266,9 @@
 chip-device-ctrl > zcl OnOff Toggle 1234 1 0
 ```
 
+**REPL Command:**
+`await devCtrl.SendCommand(1234, 1, Clusters.OnOff.Commands.Toggle())`
+
 To change the brightness of the LED, use the following command, with the level
 value somewhere between 0 and 255.
 
@@ -265,6 +276,9 @@
 chip-device-ctrl > zcl LevelControl MoveToLevel 1234 1 0 level=50
 ```
 
+**REPL Command:**
+`await devCtrl.SendCommand(1234, 1, LevelControl.Commands.MoveToLevel(level=50, transitionTime=Null, optionsMask=0, optionsOverride=0))`
+
 ### Step 7: Read basic information out of the accessory.
 
 Every Matter accessory device supports a Basic Cluster, which maintains
@@ -278,8 +292,13 @@
 chip-device-ctrl > zclread Basic SoftwareVersion 1234 1 0
 ```
 
+**REPL Command:**
+`await devCtrl.ReadAttribute(1234, [(1, Clusters.Basic.Attributes.VendorName)])`
+
 > Use the `zcl ? Basic` command to list all available commands for Basic
 > Cluster.
+>
+> In REPL, you can type `Clusters.Basic.Attributes.` and then use the TAB key.
 
 <hr>
 
@@ -289,6 +308,8 @@
 
 ### `ble-adapter-print`
 
+> BLE adapter operations is not yet supported in REPL
+
 Print the available Bluetooth adapters on device. Takes no arguments:
 
 ```
@@ -298,6 +319,8 @@
 
 ### `ble-debug-log`
 
+> BLE adapter operations is not yet supported in REPL
+
 Enable the Bluetooth LE debug logs.
 
 ```
@@ -306,6 +329,8 @@
 
 ### `ble-scan [-t <timeout>] [identifier]`
 
+> BLE adapter operations is not yet supported in REPL
+
 Start a scan action to search for valid CHIP devices over Bluetooth LE (for at
 most _timeout_ seconds). Stop when the device is matching the identifier or the
 counter times out.
@@ -336,6 +361,9 @@
 chip-device-ctrl > set-pairing-thread-credential 0e080000000000010000000300001335060004001fffe002084fe76e9a8b5edaf50708fde46f999f0698e20510d47f5027a414ffeebaefa92285cc84fa030f4f70656e5468726561642d653439630102e49c0410b92f8c7fbb4f9f3e08492ee3915fbd2f0c0402a0fff8
 ```
 
+**REPL Commands:**
+`devCtrl.SetThreadOperationalDataset(bytes.FromHex("0e080000000000010000000300001335060004001fffe002084fe76e9a8b5edaf50708fde46f999f0698e20510d47f5027a414ffeebaefa92285cc84fa030f4f70656e5468726561642d653439630102e49c0410b92f8c7fbb4f9f3e08492ee3915fbd2f0c0402a0fff8"))`
+
 ### `set-pairing-wifi-credential <ssid> <credentials>`
 
 Provides the controller with Wi-Fi network credentials that will be used in the
@@ -345,6 +373,8 @@
 chip-device-ctrl > set-pairing-wifi-credential TESTSSID P455W4RD
 ```
 
+**REPL Commands:** `devCtrl.SetWiFiCredentials('TESTSSID', 'P455W4RD')`
+
 ### `connect -ip <address> <SetUpPinCode> [<nodeid>]`
 
 Do key exchange and establish a secure session between controller and device
@@ -356,6 +386,9 @@
 
 If no nodeid given, a random Node ID will be used.
 
+**REPL Commands:**
+`devCtrl.CommissionIP(b'<ip address>', <setup pin code>, <nodeid>)`
+
 ### `connect -ble <discriminator> <SetUpPinCode> [<nodeid>]`
 
 Do key exchange and establish a secure session between controller and device
@@ -367,13 +400,20 @@
 
 If no nodeid given, a random Node ID will be used.
 
+**REPL Commands:**
+`devCtrl.ConnectBLE(<discriminator>, <setup pin code>, <nodeid>)`
+
 ### `close-session <nodeid>`
 
 If case there exists an open session (PASE or CASE) to the device with a given
 Node ID, mark it as expired.
 
+**REPL Commands:** `devCtrl.CloseSession(<nodeid>)`
+
 ### `discover`
 
+> To be implemented in REPL
+
 Discover available Matter accessory devices:
 
 ```
@@ -382,6 +422,8 @@
 
 ### `resolve <node_id>`
 
+> To be implemented in REPL
+
 Resolve DNS-SD name corresponding with the given Node ID and update address of
 the node in the device controller:
 
@@ -391,6 +433,8 @@
 
 ### `setup-payload generate [-v <Vendor ID>] [-p <Product ID>] [-cf <Custom Flow>] [-dc <Discovery Capabilities>] [-dv <Discriminator Value>] [-ps <Passcode>]`
 
+> To be implemented in REPL
+
 Print the generated Onboarding Payload Contents in human-readable (Manual
 Pairing Code) and machine-readable (QR Code) format:
 
@@ -402,6 +446,8 @@
 
 ### `setup-payload parse-manual <manual-pairing-code>`
 
+> To be implemented in REPL
+
 Print the commissioning information encoded in the Manual Pairing Code:
 
 ```
@@ -417,6 +463,8 @@
 
 ### `setup-payload parse-qr <qr-code>`
 
+> To be implemented in REPL
+
 Print the commissioning information encoded in the QR Code payload:
 
 ```
@@ -451,6 +499,14 @@
 
 For boolean type, use `key=True` or `key=False`.
 
+**REPL Commands:**
+
+```python
+# await devCtrl.SendCommand(<nodeid>, <endpoint>, Clusters.<cluster>.Commands.<command>(<arguments>))
+# e.g.
+await devCtrl.SendCommand(12344321, 1, Clusters.LevelControl.Commands.MoveWithOnOff(moveMode=1, rate=2, optionsMask=0, optionsOverride=0))
+```
+
 ### `zcl ?`
 
 List available clusters:
@@ -499,6 +555,10 @@
 WindowCovering
 ```
 
+**REPL Commands**
+
+Type `Clusters.` and hit TAB
+
 ### `zcl ? <Cluster>`
 
 List available commands in cluster. For example, for _Basic_ cluster:
@@ -525,6 +585,10 @@
 ClusterRevision
 ```
 
+**REPL Commands**
+
+Type `Clusters.(cluster name).Commands.` and hit TAB
+
 ### `zclread <Cluster> <Attribute> <NodeId> <EndpointId> <GroupId> [arguments]`
 
 Read the value of ZCL attribute. For example:
@@ -533,6 +597,14 @@
 chip-device-ctrl > zclread Basic VendorName 1234 1 0
 ```
 
+**REPL Commands**
+
+```python
+# devCtrl.ReadAttribute(<nodeid>, [(<endpoint id>, Clusters.<cluster>.Attributes.<attribute>)])
+# e.g.
+await devCtrl.ReadAttribute(1234, [(1, Clusters.Basic.Attributes.VendorName)])
+```
+
 ### `zclwrite <cluster> <attribute> <nodeid> <endpoint> <groupid> <value>`
 
 Write the value to a ZCL attribute. For example:
@@ -547,6 +619,17 @@
 Note: The format of the value is the same as the format of argument values for
 ZCL cluster commands.
 
+**REPL Commands**
+
+```python
+# devCtrl.WriteAttribute(<nodeid>, [(<endpointid>, Clusters.<cluster>.Attributes.<attribute>(value=<attribute value>))])
+# e.g.
+await devCtrl.WriteAttribute(1, [(1, Clusters.TestCluster.Attributes.Int8u(value=1))])
+await devCtrl.WriteAttribute(1, [(1, Clusters.TestCluster.Attributes.Boolean(value=True))])
+await devCtrl.WriteAttribute(1, [(1, Clusters.TestCluster.Attributes.OctetString(value=b'123123\x00'))])
+await devCtrl.WriteAttribute(1, [(1, Clusters.TestCluster.Attributes.CharString(value='233233'))])
+```
+
 ### `zclsubscribe <Cluster> <Attribute> <Nodeid> <Endpoint> <MinInterval> <MaxInterval>`
 
 Configure ZCL attribute reporting settings. For example:
@@ -555,6 +638,14 @@
 chip-device-ctrl > zclsubscribe OccupancySensing Occupancy 1234 1 10 20
 ```
 
+**REPL Commands**
+
+```python
+# devCtrl.ReadAttribute(<nodeid>, [(<endpoint>, Clusters.<cluster>.Attributes.<attribute>)], reportInterval=(<min interval>, <max interval>))
+# e.g.
+await devCtrl.ReadAttribute(1, [(1, Clusters.OccupancySensing.Attributes.Occupancy)], reportInterval=(10, 20))
+```
+
 ### `zclsubscribe -shutdown <subscription id>`
 
 Shutdown an existing attribute subscription.
@@ -583,3 +674,12 @@
 ```
 
 The subscription id is `0xdeadbeefcafe` in this case
+
+**REPL Commands**
+
+```python
+# SubscriptionTransaction.Shutdown()
+# e.g.
+sub = await devCtrl.ReadAttribute(1, [(1, Clusters.OccupancySensing.Attributes.Occupancy)], reportInterval=(10, 20))
+sub.Shutdown()
+```
diff --git a/src/controller/python/chip-device-ctrl.py b/src/controller/python/chip-device-ctrl.py
index 5b567b3..8b8202a 100755
--- a/src/controller/python/chip-device-ctrl.py
+++ b/src/controller/python/chip-device-ctrl.py
@@ -25,11 +25,14 @@
 
 from __future__ import absolute_import
 from __future__ import print_function
+from sqlite3 import adapt
 from chip import ChipDeviceCtrl
+from chip import clusters as Clusters
 from chip import FabricAdmin
 from chip import ChipStack
 from chip import ChipCommissionableNodeCtrl
 from chip import exceptions
+from chip import native
 import argparse
 import ctypes
 import sys
@@ -156,6 +159,7 @@
 class DeviceMgrCmd(Cmd):
     def __init__(self, rendezvousAddr=None, controllerNodeId=1, bluetoothAdapter=None):
         self.lastNetworkId = None
+        self.replHint = None
 
         pretty.install(indent_guides=True, expand_all=True)
 
@@ -181,7 +185,7 @@
 
         self.chipStack = ChipStack.ChipStack(
             bluetoothAdapter=bluetoothAdapter, persistentStoragePath='/tmp/chip-device-ctrl-storage.json')
-        self.fabricAdmin = FabricAdmin.FabricAdmin()
+        self.fabricAdmin = FabricAdmin.FabricAdmin(0xFFF1)
         self.devCtrl = self.fabricAdmin.NewController(
             nodeId=controllerNodeId, useTestCommissioner=True)
 
@@ -267,6 +271,11 @@
         return line
 
     def postcmd(self, stop, line):
+        if self.replHint is not None:
+            print("Try the following command in repl: ")
+            print(self.replHint)
+            print("")
+        self.replHint = None
         if not stop and self.use_rawinput:
             self.prompt = "chip-device-ctrl > "
         return stop
@@ -545,12 +554,11 @@
                 print("Usage:")
                 self.do_help("paseonly")
                 return
-
             nodeid = random.randint(1, 1000000)  # Just a random number
             if len(args) == 4:
                 nodeid = int(args[3])
             print("Device is assigned with nodeid = {}".format(nodeid))
-
+            self.replHint = f"devCtrl.EstablishPASESessionIP({repr(args[1])}, {int(args[2])}, {nodeid})"
             if args[0] == "-ip" and len(args) >= 3:
                 self.devCtrl.EstablishPASESessionIP(args[1], int(args[2]), nodeid)
             else:
@@ -575,8 +583,8 @@
                 print("Usage:")
                 self.do_help("commission")
                 return
-
             nodeid = int(args[0])
+            self.replHint = f"devCtrl.Commission({nodeid})"
             self.devCtrl.Commission(nodeid)
         except Exception as ex:
             print(str(ex))
@@ -613,8 +621,10 @@
             print("Device is assigned with nodeid = {}".format(nodeid))
 
             if args[0] == "-ip" and len(args) >= 3:
+                self.replHint = f"devCtrl.CommissionIP({repr(args[1])}, {int(args[2])}, {nodeid})"
                 self.devCtrl.CommissionIP(args[1], int(args[2]), nodeid)
             elif args[0] == "-ble" and len(args) >= 3:
+                self.replHint = f"devCtrl.ConnectBLE({int(args[1])}, {int(args[2])}, {nodeid})"
                 self.devCtrl.ConnectBLE(int(args[1]), int(args[2]), nodeid)
             elif args[0] in ['-qr', '-code'] and len(args) >= 2:
                 if len(args) == 3:
@@ -632,6 +642,7 @@
                     print("No rendezvous information provided, default to all.")
                     setupPayload.attributes["RendezvousInformation"] = 0b111
                 setupPayload.Print()
+                self.replHint = f"devCtrl.CommissionWithCode(setupPayload={repr(setupPayload)}, nodeid={nodeid})"
                 self.ConnectFromSetupPayload(setupPayload, nodeid)
             else:
                 print("Usage:")
@@ -653,7 +664,7 @@
             parser = argparse.ArgumentParser()
             parser.add_argument('nodeid', type=int, help='Peer node ID')
             args = parser.parse_args(shlex.split(line))
-
+            self.replHint = f"devCtrl.CloseSession({args.nodeid})"
             self.devCtrl.CloseSession(args.nodeid)
         except exceptions.ChipStackException as ex:
             print(str(ex))
@@ -671,6 +682,7 @@
             args = shlex.split(line)
             if len(args) == 1:
                 try:
+                    self.replHint = f"devCtrl.ResolveNode({int(args[0])});devCtrl.GetAddressAndPort({int(args[0])})"
                     self.devCtrl.ResolveNode(int(args[0]))
                     address = self.devCtrl.GetAddressAndPort(int(args[0]))
                     address = "{}:{}".format(
@@ -815,6 +827,8 @@
                 # When command takes no arguments, (not command) is True
                 if command is None:
                     raise exceptions.UnknownCommand(args[0], args[1])
+                req = eval(f"Clusters.{args[0]}.Commands.{args[1]}")(**FormatZCLArguments(args[5:], command))
+                self.replHint = f"await devCtrl.SendCommand({int(args[2])}, {int(args[3])}, Clusters.{repr(req)})"
                 err, res = self.devCtrl.ZCLSend(args[0], args[1], int(
                     args[2]), int(args[3]), int(args[4]), FormatZCLArguments(args[5:], command), blocking=True)
                 if err != 0:
@@ -851,6 +865,7 @@
             elif len(args) == 5:
                 if args[0] not in all_attrs:
                     raise exceptions.UnknownCluster(args[0])
+                self.replHint = f"await devCtrl.ReadAttribute({int(args[2])}, [({int(args[3])}, Clusters.{args[0]}.Attributes.{args[1]})])"
                 res = self.devCtrl.ZCLReadAttribute(args[0], args[1], int(
                     args[2]), int(args[3]), int(args[4]))
                 if res != None:
@@ -885,6 +900,7 @@
                     raise exceptions.UnknownCluster(args[0])
                 attribute_type = all_attrs.get(args[0], {}).get(
                     args[1], {}).get("type", None)
+                self.replHint = f"await devCtrl.WriteAttribute({int(args[2])}, [({int(args[3])}, Clusters.{args[0]}.Attributes.{args[1]}(value={repr(ParseValueWithType(args[5], attribute_type))}))])"
                 res = self.devCtrl.ZCLWriteAttribute(args[0], args[1], int(
                     args[2]), int(args[3]), int(args[4]), ParseValueWithType(args[5], attribute_type))
                 print(repr(res))
@@ -921,10 +937,12 @@
                     raise exceptions.UnknownCluster(args[0])
                 res = self.devCtrl.ZCLSubscribeAttribute(args[0], args[1], int(
                     args[2]), int(args[3]), int(args[4]), int(args[5]))
+                self.replHint = f"sub = await devCtrl.ReadAttribute({int(args[2])}, [({int(args[3])}, Clusters.{args[0]}.Attributes.{args[1]})], reportInterval=({int(args[4])}, {int(args[5])}))"
                 print(res.GetAllValues())
                 print(f"Subscription Established: {res}")
             elif len(args) == 2 and args[0] == '-shutdown':
                 subscriptionId = int(args[1], base=0)
+                self.replHint = "You can call sub.Shutdown() (sub is the return value of ReadAttribute() called before)"
                 self.devCtrl.ZCLShutdownSubscription(subscriptionId)
             else:
                 self.do_help("zclsubscribe")
@@ -947,6 +965,7 @@
                 return
             self.devCtrl.SetWiFiCredentials(
                 args[0], args[1])
+            self.replHint = f"devCtrl.SetWiFiCredentials({repr(args[0])}, {repr(args[1])})"
         except Exception as ex:
             print(str(ex))
             return
@@ -961,6 +980,7 @@
                 print("Usage:")
                 self.do_help("set-pairing-thread-credential")
                 return
+            self.replHint = f"devCtrl.SetThreadOperationalDataset(bytes.fromhex({repr(args[0])}))"
             self.devCtrl.SetThreadOperationalDataset(bytes.fromhex(args[0]))
         except Exception as ex:
             print(str(ex))
@@ -1000,6 +1020,8 @@
                 print("Invalid option specified!")
                 raise ValueError("Invalid option specified")
 
+            self.replHint = f"devCtrl.OpenCommissioningWindow(nodeid={int(arglist[0])}, timeout={args.timeout}, iteration={args.iteration}, discriminator={args.discriminator}, option={args.option})"
+
             self.devCtrl.OpenCommissioningWindow(
                 int(arglist[0]), args.timeout, args.iteration, args.discriminator, args.option)
 
@@ -1025,6 +1047,8 @@
 
             compressed_fabricid = self.devCtrl.GetCompressedFabricId()
             raw_fabricid = self.devCtrl.fabricId
+
+            self.replHint = f"devCtrl.GetCompressedFabricId(), devCtrl.fabricId"
         except exceptions.ChipStackException as ex:
             print("An exception occurred during reading FabricID:")
             print(str(ex))
@@ -1133,7 +1157,7 @@
                 print(
                     "Invalid bluetooth adapter: {}, adapter name looks like hci0, hci1 etc.")
                 sys.exit(-1)
-
+    native.Init(bluetoothAdapter=adapterId)
     try:
         devMgrCmd = DeviceMgrCmd(rendezvousAddr=options.rendezvousAddr,
                                  controllerNodeId=options.controllerNodeId, bluetoothAdapter=adapterId)
@@ -1160,4 +1184,17 @@
 
 
 if __name__ == "__main__":
+    print("""
+    chip-device-ctrl will be deprecated and will be removed in the future. Please try chip-repl, which provides a lot of features.
+
+    - Multi-fabric support,
+    - Better complex type support for sending commands,
+    - Native command highlight,
+    - Parallel commands with asyncio,
+    - Writing complex logic inline.
+
+    You can still use chip-device-ctrl as usual for now, and you will learn how to do the same thing in chip-repl.
+
+    Feel free to file an issue if some features are not supported by chip-repl yet.
+    """)
     main()