import os, subprocess

if os.path.isfile('/tmp/repl-storage.json'):
    os.remove('/tmp/repl-storage.json')

# So that the all-clusters-app won't boot with stale prior state.
os.system('rm -rf /tmp/chip_*')

"""
Explanation: Interaction Model Examples
<a href="http://35.236.121.59/hub/user-redirect/git-pull?repo=https%3A%2F%2Fgithub.com%2Fproject-chip%2Fconnectedhomeip&urlpath=lab%2Ftree%2Fconnectedhomeip%2Fdocs%2Fguides%2Frepl%2FMatter%2520-%2520Basic%2520Interactions.ipynb&branch=master">
<img src="https://i.ibb.co/hR3yWsC/launch-playground.png" alt="drawing" width="130"/>
</a>
<br></br>
This walks through the various interactions that can be initiated from the REPL towards a target using the Matter Interaction Model (IM) and Data Model (DM).
Clear Persisted Storage
Let's clear out our persisted storage (if one exists) to start from a clean slate.
End of explanation
"""


import chip.native
import pkgutil
module = pkgutil.get_loader('chip.ChipReplStartup')
%run {module.path}

"""
Explanation: Initialization
Let's first begin by setting up by importing some key modules that are needed to make it easier for us to interact with the Matter stack.
ChipReplStartup.py is run within the global namespace. This results in all of its imports being made available here.

NOTE: This is not needed if you launch the REPL from the command-line.
End of explanation
"""


v = Clusters.TestCluster.Structs.SimpleStruct()
v.a = 20
v.b = True
v.c = Clusters.TestCluster.Enums.SimpleEnum.kValueA
v.d = b'1234'
v.e = 30
v.g = 23.234

v

"""
Explanation: Cluster Elements
The Interaction Model uses data model types that refer not just to the various base types defines in the spec, but types that correspond to structs/commands/events/attributes defined in each cluster specification. The cluster-specific types are referred to as 'cluster objects'. These are represented as Python dataclasses, with each field in the respective object equivalently named as a member within the dataclass.
Namespaces
Objects in clusters are organized into namespaces. All clusters can be found under the Clusters namespace, with the appropriate cluster in upper camel case within that. (e.g Clusters.TestCluster).
Within that, Commands, Structs and Attributes delimit the respective types in the cluster.
Example Struct:
End of explanation
"""


Clusters.TestCluster.Commands.TestAddArguments()

"""
Explanation: Example Command:
End of explanation
"""


matterhelp(Clusters.TestCluster.Commands.TestAddArguments)

"""
Explanation: To get more information about the fields in these objects and their types, run:
End of explanation
"""


a = Clusters.TestCluster.Structs.NullablesAndOptionalsStruct()
a.nullableInt = Clusters.Types.NullValue
a

"""
Explanation: Nullable Fields
For fields that are nullable, they are represented as a Typing.Union[Nullable, ...]. This means that it can either be a Nullable type or the underlying type of the field.
When nullable, a field can either take on the value of the native type, or a value of NullValue.
End of explanation
"""


print(a.nullableOptionalInt)

"""
Explanation: Optional Fields
If a field is optional, it is represented in the typing hints as a Typing.Union[NoneType, ...]. An optional field that isn't present has a value of None.
End of explanation
"""


Clusters.TestCluster.Structs.SimpleStruct()

"""
Explanation: Defaults
Upon construction of a cluster object, the fields within that object are automatically initialized to type specific defaults as specified in the data model specification:
End of explanation
"""


import time, os
import subprocess

# So that the all-clusters-app won't boot with stale prior state.    
os.system('pkill -f chip-all-clusters-app')

# The location of the all-clusters-app in the cloud playground is one level higher - adjust for this by testing for file presence.
if (os.path.isfile('../../../out/debug/chip-all-clusters-app')):
    appPath = '../../../out/debug/chip-all-clusters-app'
else:
    appPath = '../../../../out/debug/chip-all-clusters-app'
    
process = subprocess.Popen(appPath, stdout=subprocess.DEVNULL)
time.sleep(1)

"""
Explanation: IM Interactions
This section will walk through the various types of IM Interactions that are possible in the REPL.
Commission and Setup Server
Launch Server
Let's launch an instance of the chip-all-clusters-app.

NOTE: If you're interacting with real devices, this step can be skipped.
End of explanation
"""


devCtrl.CommissionIP(b'127.0.0.1', 20202021, 2)

"""
Explanation: Commission Target (Locally Launched App)
Commission the target with a NodeId of 1.
End of explanation
"""


devCtrl.CommissionThread(3840, 20202021, 2, b'\x01\x03\xff')

"""
Explanation: Commission Target (BLE + Thread)
To commission a Thread-based target over BLE, ensure your BLE stack is up on your host and available as hci0 on Linux. You can confirm this by running hciconfig -a. You'll also need Thread credentials to join the Thread network.

NOTE: MacOS Monterey is currently not supported due to issues with its BLE stack.
End of explanation
"""


devCtrl.CommissionWiFi(3840, 20202021, 2, 'MyWifiSsid', 'MyWifiPassword')

"""
Explanation: Commission Target (BLE + WiFi)
To commission a Wifi-based target over BLE, ensure your BLE stack is up on your host and available as hci0 on Linux. You can confirm this by running hciconfig -a. You'll also need Wifi credentials to join the Thread network.

NOTE: MacOS Monterey is currently not supported due to issues with its BLE stack.
End of explanation
"""


await devCtrl.SendCommand(2, 1, Clusters.OnOff.Commands.On())

"""
Explanation: Invoke Interaction
Basic Command (Success Response)
Let's send a basic command to turn on/off the light on Endpoint 1.
End of explanation
"""


try:
    await devCtrl.SendCommand(2, 100, Clusters.OnOff.Commands.On())
except Exception as e:
    pprint(e)

"""
Explanation: The receipt of a successful status response will result in the command just returning successfully. Otherwise, an exception will be thrown.
Basic Command (Failure Response)
If we send the same command to an invalid endpoint, an exception is thrown. If an IM status code was received from the server, a InteractionModelError is thrown containing the IM status code:
End of explanation
"""


await devCtrl.SendCommand(2, 1, Clusters.TestCluster.Commands.TestListInt8UReverseRequest([1, 3, 5, 7]))

"""
Explanation: Basic Command (Data Response)
Here's an example of a command that sends back a data response, and how that is presented:
End of explanation
"""


a = await devCtrl.ReadAttribute(2, [Clusters.TestCluster.Attributes.Int16u])
a

a[1][Clusters.TestCluster]

a[1][Clusters.TestCluster][Clusters.TestCluster.Attributes.Int16u]

"""
Explanation: Read Interaction
The ReadAttribute method on the DeviceController class can be used to read attributes from a target. The NodeId of the target is the first argument, followed by a list of paths that are expressed as cluster object namespaces to the respective slices of the data that is requested.
By default, the data is returned as a dictionary, with the top-level item representing the endpoint, then the cluster and the attribute. The latter two keys are expressed using cluster object namespaces.
Read 1 attribute:
End of explanation
"""


await devCtrl.ReadAttribute(2, [Clusters.TestCluster.Attributes.Int16u, Clusters.TestCluster.Attributes.Boolean])

"""
Explanation: Read 2 attributes:
End of explanation
"""


await devCtrl.ReadAttribute(2, [(1, Clusters.OnOff)])

"""
Explanation: Read the entirety of a cluster on an endpoint:
The path is represented as tuple of (endpoint, cluster)
End of explanation
"""


await devCtrl.ReadAttribute(2, [Clusters.OnOff])

"""
Explanation: Read the entirety of a cluster across all endpoints:
End of explanation
"""


await devCtrl.ReadAttribute(2, [2])

"""
Explanation: Read an endpoint:
End of explanation
"""


await devCtrl.ReadAttribute(2, [('*')])

"""
Explanation: Read the entire node:
End of explanation
"""


await devCtrl.ReadAttribute(2, [2], True)

"""
Explanation: Alternative 'Cluster' View
The above encapsulates each attribute as a 'cluster-object' key within the top-level cluster instance. Instead, an alternative view each attribute is represented as a field in the object can be retrieved by passing in True to the third argument:
End of explanation
"""


# Force an event to get emitted.
await devCtrl.SendCommand(2, 1, Clusters.TestCluster.Commands.TestEmitTestEventRequest())

await devCtrl.ReadEvent(2, [('*')])

"""
Explanation: Read Events:
A ReadEvents API exists that behaves similarly to the ReadAttributes API. It permits the same degrees of wildcard expression as its counterpart and follows the same format for expressing all wildcard permutations.
Read all events:
End of explanation
"""


reportingTimingParams = (0, 2) # MinInterval = 0s, MaxInterval = 2s
subscription = await devCtrl.ReadAttribute(2, [(2, Clusters.OnOff)], True, reportingTimingParams)
subscription

subscription.GetAttributes()

"""
Explanation: Subscription Interaction
To subscribe to a Node, the same ReadAttributes API is used to trigger a subscription, with a valid reportInterval tuple passed in being used as a way to indicate the request to create a subscription.
End of explanation
"""


await devCtrl.SendCommand(2, 2, Clusters.OnOff.Commands.On())
time.sleep(1)

await devCtrl.SendCommand(2, 2, Clusters.OnOff.Commands.Off())
time.sleep(1)

subscription.Shutdown()

"""
Explanation: Trigger Report
To trigger a report, let's alter the state of the on/off switch on EP1. That should trigger the generation of a set of attribute reports.
The SubscriptionTransaction object returned by ReadAttribute permits installing a callback that is invoked on any attribute report. A default callback is installed above that just dumps out the attribute data.
End of explanation
"""


reportingTimingParams = (0, 2) # MinInterval = 0s, MaxInterval = 2s
subscription = await devCtrl.ReadEvent(2, [()], reportingTimingParams)

subscription.GetEvents()

"""
Explanation: Subscribe to Events
End of explanation
"""


await devCtrl.SendCommand(2, 1, Clusters.TestCluster.Commands.TestEmitTestEventRequest())
time.sleep(3)

subscription.Shutdown()

"""
Explanation: Trigger Event
Force an event to get emitted, which after a short while, should generate a report and trigger the print out of the received event:
End of explanation
"""


await devCtrl.WriteAttribute(2, [ (1, Clusters.TestCluster.Attributes.Int16u(2)) ])

await devCtrl.ReadAttribute(2, [ (1, Clusters.TestCluster.Attributes.Int16u) ])

"""
Explanation: Write Interaction
To write attribute data, the WriteAttribute API can be used. It requires a NodeId and a list of cluster object encapsulated data for the attribute being written.
End of explanation
"""


