Back to Connectedhomeip

Interaction Model Examples

docs/development_controllers/matter-repl/Matter_Basic_Interactions.ipynb

1.5.1.010.9 KB
Original Source

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"> </a> </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.

python
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_*')

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.

ReplStartup.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.

python
%reset -f
import importlib.util
spec = importlib.util.find_spec('matter.ReplStartup')
%run {spec.origin}

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.UnitTesting).

Within that, Commands, Structs and Attributes delimit the respective types in the cluster.

Example Struct:

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

v

Example Command:

python
Clusters.UnitTesting.Commands.TestAddArguments()

To get more information about the fields in these objects and their types, run:

python
matterhelp(Clusters.UnitTesting.Commands.TestAddArguments)

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.

python
a = Clusters.UnitTesting.Structs.NullablesAndOptionalsStruct()
a.nullableInt = Clusters.Types.NullValue
a

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.

python
print(a.nullableOptionalInt)

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:

python
Clusters.UnitTesting.Structs.SimpleStruct()

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.

python
import time, os
import subprocess
os.system('pkill -f chip-all-clusters-app')
time.sleep(1)

appPath = '../../../out/linux-x64-all-clusters/chip-all-clusters-app'

process = subprocess.Popen(appPath, stdout=subprocess.DEVNULL)
time.sleep(1)

Discover and commission commissionable node

python
devices = await devCtrl.DiscoverCommissionableNodes(filterType=matter.discovery.FilterType.LONG_DISCRIMINATOR, filter=3840, stopOnFirst=True, timeoutSecond=2)
devices

You can find a list of discovered device

You can call Commission(nodeId, setupPinCode) on one of the returned object:

python
await devices[0].Commission(2, 20202021)

The device will be commissioned by the DeviceController instance that discovered it (caIndex(1)/fabricId(0x0000000000000001)/nodeId(0x000000000001B669) in this case).

Commission Target (Locally Launched App)

Commission the target with a NodeId of 2.

WARNING: The device can only be commissioned once. Repeating the commissioning process will result in Errors. The line %%script true has been added to bypass execution errors and allow the notebook to run automatically.

If you wish to test the behaviour of the function, remove %%script true

python
%%script true

await devCtrl.CommissionOnNetwork(2, 20202021)

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.

python
%%script true

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

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 WiFi network.

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

python
%%script true

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

Invoke Interaction

Basic Command (Success Response)

Let's send a basic command to turn on/off the light on Endpoint 1.

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

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:

python
from pprint import pprint

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

Basic Command (Data Response)

Here's an example of a command that sends back a data response, and how that is presented:

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

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:

python
a = await devCtrl.ReadAttribute(2, [Clusters.UnitTesting.Attributes.Int16u])
a
python
a[1][Clusters.UnitTesting]
python
a[1][Clusters.UnitTesting][Clusters.UnitTesting.Attributes.Int16u]

Read 2 attributes:

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

Read the entirety of a cluster on an endpoint:

The path is represented as tuple of (endpoint, cluster)

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

Read the entirety of a cluster across all endpoints:

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

Read an endpoint:

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

Read the entire node:

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

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 argument returnClusterObject:

python
await devCtrl.ReadAttribute(2, [2], returnClusterObject=True)

Read Events:

A ReadEvent API exists that behaves similarly to the ReadAttribute 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:

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

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

Subscription Interaction

To subscribe to a Node, the same ReadAttribute 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.

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

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.

python
await devCtrl.SendCommand(2, 2, Clusters.OnOff.Commands.On())
time.sleep(1)
python
await devCtrl.SendCommand(2, 2, Clusters.OnOff.Commands.Off())
time.sleep(1)
python
subscription.Shutdown()

Subscribe to Events

python
reportingTimingParams = (0, 2) # MinInterval = 0s, MaxInterval = 2s
# Subscribing to Events from EndPoint 1
subscription = await devCtrl.ReadEvent(2, [ 1 ], reportInterval = reportingTimingParams)
python
subscription.GetEvents()

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:

python
await devCtrl.SendCommand(2, 1, Clusters.UnitTesting.Commands.TestEmitTestEventRequest())
time.sleep(3)
python
subscription.Shutdown()

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.

python
await devCtrl.WriteAttribute(2, [ (1, Clusters.UnitTesting.Attributes.Int16u(2)) ])
python
await devCtrl.ReadAttribute(2, [ (1, Clusters.UnitTesting.Attributes.Int16u) ])