Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Update Python controller bindings with latest patches #72

Merged
merged 2 commits into from
Jun 7, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions 0001-Support-custom-platform-tag.patch
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
From d06671d9031156ce3b29e06d7edb344d8f6ee0d6 Mon Sep 17 00:00:00 2001
Message-ID: <d06671d9031156ce3b29e06d7edb344d8f6ee0d6.1698087175.git.stefan@agner.ch>
From bcd84a21c67a681fcaba43118d9d9bb7ed618b55 Mon Sep 17 00:00:00 2001
From: Stefan Agner <stefan@agner.ch>
Date: Tue, 22 Nov 2022 10:51:17 +0100
Subject: [PATCH] Support custom platform tag
@@ -9,7 +8,7 @@ Subject: [PATCH] Support custom platform tag
1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/src/controller/python/BUILD.gn b/src/controller/python/BUILD.gn
index 4b84c63d5f..b01dafbc30 100644
index 5fc2212098..caa33c3a40 100644
--- a/src/controller/python/BUILD.gn
+++ b/src/controller/python/BUILD.gn
@@ -35,6 +35,15 @@ declare_args() {
@@ -28,7 +27,7 @@ index 4b84c63d5f..b01dafbc30 100644
}

shared_library("ChipDeviceCtrl") {
@@ -340,16 +349,7 @@ chip_python_wheel_action("chip-core") {
@@ -344,16 +353,7 @@ chip_python_wheel_action("chip-core") {
cpu_tag = current_cpu
}

@@ -47,5 +46,5 @@ index 4b84c63d5f..b01dafbc30 100644
tags = "cp37-abi3-" + py_platform_tag

--
2.42.0
2.45.2

9 changes: 3 additions & 6 deletions 0002-Use-data-as-platform-storage-location.patch
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
From 88ba0f8233f97a9bb773341da7c996deee9675fe Mon Sep 17 00:00:00 2001
Message-ID: <88ba0f8233f97a9bb773341da7c996deee9675fe.1698087175.git.stefan@agner.ch>
In-Reply-To: <d06671d9031156ce3b29e06d7edb344d8f6ee0d6.1698087175.git.stefan@agner.ch>
References: <d06671d9031156ce3b29e06d7edb344d8f6ee0d6.1698087175.git.stefan@agner.ch>
From 5c1316070604e8e7ade9b518ad717c63276dd06a Mon Sep 17 00:00:00 2001
From: Stefan Agner <stefan@agner.ch>
Date: Fri, 27 May 2022 16:38:14 +0200
Subject: [PATCH] Use /data as platform storage location
@@ -11,7 +8,7 @@ Subject: [PATCH] Use /data as platform storage location
1 file changed, 6 insertions(+)

diff --git a/src/platform/Linux/BUILD.gn b/src/platform/Linux/BUILD.gn
index a2cfa6b39c..f6fd74ab0c 100644
index d73a2dcb0f..97c397994e 100644
--- a/src/platform/Linux/BUILD.gn
+++ b/src/platform/Linux/BUILD.gn
@@ -38,6 +38,12 @@ if (chip_mdns == "platform") {
@@ -28,5 +25,5 @@ index a2cfa6b39c..f6fd74ab0c 100644
"../DeviceSafeQueue.cpp",
"../DeviceSafeQueue.h",
--
2.42.0
2.45.2

4 changes: 2 additions & 2 deletions 0003-Linux-Increase-number-of-endpoints.patch
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
From 47ca82473af8d77eb89732884542c719ccdd9312 Mon Sep 17 00:00:00 2001
From 3bcfcd1d91050868d0153bf7692db3ac844ccc94 Mon Sep 17 00:00:00 2001
From: Stefan Agner <stefan@agner.ch>
Date: Thu, 29 Feb 2024 19:07:15 +0100
Subject: [PATCH] Linux: Increase number of endpoints
@@ -36,5 +36,5 @@ index 3aab9a7b9b..02e664eddc 100644

// On linux platform, we have sys/socket.h, so HAVE_SO_BINDTODEVICE should be set to 1
--
2.44.0
2.45.2

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
From f9fc067ad51d3989a2045f19fc5641971ce1ee20 Mon Sep 17 00:00:00 2001
From 7ce75765081aeebef5f3adc397dff4db256348eb Mon Sep 17 00:00:00 2001
From: Stefan Agner <stefan@agner.ch>
Date: Wed, 27 Mar 2024 22:13:19 +0100
Subject: [PATCH] [Python] Implement async friendly GetConnectedDevice
Subject: [PATCH] Implement async friendly GetConnectedDevice

Currently GetConnectedDeviceSync() is blocking e.g. when a new session
needs to be created. This is not asyncio friendly as it blocks the
@@ -129,5 +129,5 @@ index 369260787d..b3d0aa2d7f 100644
v) for v in attributes] if attributes else None
clusterDataVersionFilters = [self._parseDataVersionFilterTuple(
--
2.44.0
2.45.2

4 changes: 2 additions & 2 deletions 0005-Enable-node-ID-logging-in-exchanges.patch
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
From 3c551b7a706428d2fde77df0bc93d81bb37a2022 Mon Sep 17 00:00:00 2001
From c1536db9448763f2e45b3aeef34f30cbfb41889e Mon Sep 17 00:00:00 2001
From: Stefan Agner <stefan@agner.ch>
Date: Thu, 18 Apr 2024 21:46:59 +0200
Subject: [PATCH] Enable node ID logging in exchanges
@@ -22,5 +22,5 @@ index 558e0ee08e..5a3f5facb7 100644

/**
--
2.44.0
2.45.2

Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
From dce7020f3a9e542e6afbc59f131c77210df5f7db Mon Sep 17 00:00:00 2001
Message-ID: <dce7020f3a9e542e6afbc59f131c77210df5f7db.1714067071.git.stefan@agner.ch>
From 113143a945bbe5663b78b59b05d6b00670d79292 Mon Sep 17 00:00:00 2001
From: Stefan Agner <stefan@agner.ch>
Date: Thu, 25 Apr 2024 15:19:17 +0200
Subject: [PATCH] [Python] Fix OnRead[Event|Attribute]DataCallback for Arm64
Apple Patform devices
Subject: [PATCH] Fix OnRead[Event|Attribute]DataCallback for Arm64 Apple
Patform devices

On M1/Arm64 macOS systems, the OnReadEventDataCallback often returned
an invalid status, e.g.:
@@ -50,5 +49,5 @@ index e31f3431b8..b73b4a49b4 100644
// When the apData is nullptr, means we did not receive a valid event data from server, status will be some error
// status.
--
2.44.0
2.45.2

Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
From 9bc05af1e1ef2ec93336dc0eecba16b6802b6fb1 Mon Sep 17 00:00:00 2001
Message-ID: <9bc05af1e1ef2ec93336dc0eecba16b6802b6fb1.1716466458.git.stefan@agner.ch>
From 4fe4daf7f165dc007ab6092bee2adef67650d129 Mon Sep 17 00:00:00 2001
From: Stefan Agner <stefan@agner.ch>
Date: Thu, 23 May 2024 12:48:54 +0200
Subject: [PATCH] [Python] Add raw attribute callback
Subject: [PATCH] Add raw attribute callback

Add new subscription callback which uses raw AttributePath as paths
of changed attributes. This allows to subscribe to custom clusters,
@@ -102,5 +101,5 @@ index 9e46eed469..ce522bf452 100644
# Clear it out once we've notified of all changes in this transaction.
self._changedPathSet = set()
--
2.45.1
2.45.2

594 changes: 594 additions & 0 deletions 0008-Python-Eliminate-ZCLSubscribeAttribute-33337.patch

Large diffs are not rendered by default.

589 changes: 589 additions & 0 deletions 0009-Python-Eliminate-ZCLReadAttribute-ZCLSend-33428.patch

Large diffs are not rendered by default.

671 changes: 671 additions & 0 deletions 0010-Python-Create-pairingDelegate-for-each-DeviceControl.patch

Large diffs are not rendered by default.

359 changes: 359 additions & 0 deletions 0011-Python-Call-SDK-asyncio-friendly-32764.patch

Large diffs are not rendered by default.

308 changes: 308 additions & 0 deletions 0012-Python-Make-AttributePath-more-pythonic-33571.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
From 518fdbac13ace67cfbd4482286320f4c45ab1b05 Mon Sep 17 00:00:00 2001
From: Stefan Agner <stefan@agner.ch>
Date: Fri, 24 May 2024 10:39:41 +0200
Subject: [PATCH] [Python] Make AttributePath more pythonic (#33571)

* [Python] Make AttributePath more pythonic

Use dataclass default initializer to initialize AttributePath. Use
static method to initialize from Cluster or Attribute.

Also hash the integer fields directly, this is more efficient than
formatting a string first.

* Drop AttributePathWithListIndex

Drop AttributePathWithListIndex as it is unused.

* Make DataVersionFilter/EventPath pythonic as well

Use frozen data classes and static initializers similar to
AttributePath.

* Fix _parseEventPathTuple

* Fix _parseDataVersionFilterTuple
---
src/controller/python/chip/ChipDeviceCtrl.py | 47 +++------
.../python/chip/clusters/Attribute.py | 97 ++++++-------------
.../test/test_scripts/cluster_objects.py | 2 +-
3 files changed, 48 insertions(+), 98 deletions(-)

diff --git a/src/controller/python/chip/ChipDeviceCtrl.py b/src/controller/python/chip/ChipDeviceCtrl.py
index 3282ffd191..4d14a42f18 100644
--- a/src/controller/python/chip/ChipDeviceCtrl.py
+++ b/src/controller/python/chip/ChipDeviceCtrl.py
@@ -1154,33 +1154,26 @@ class ChipDeviceControllerBase():
# Concrete path
typing.Tuple[int, typing.Type[ClusterObjects.ClusterAttributeDescriptor]]
]):
- endpoint = None
- cluster = None
- attribute = None
-
if pathTuple == ('*') or pathTuple == ():
# Wildcard
- pass
+ return ClusterAttribute.AttributePath()
elif not isinstance(pathTuple, tuple):
if isinstance(pathTuple, int):
- endpoint = pathTuple
+ return ClusterAttribute.AttributePath(EndpointId=pathTuple)
elif issubclass(pathTuple, ClusterObjects.Cluster):
- cluster = pathTuple
+ return ClusterAttribute.AttributePath.from_cluster(EndpointId=None, Cluster=pathTuple)
elif issubclass(pathTuple, ClusterObjects.ClusterAttributeDescriptor):
- attribute = pathTuple
+ return ClusterAttribute.AttributePath.from_attribute(EndpointId=None, Attribute=pathTuple)
else:
raise ValueError("Unsupported Attribute Path")
else:
# endpoint + (cluster) attribute / endpoint + cluster
- endpoint = pathTuple[0]
if issubclass(pathTuple[1], ClusterObjects.Cluster):
- cluster = pathTuple[1]
+ return ClusterAttribute.AttributePath.from_cluster(EndpointId=pathTuple[0], Cluster=pathTuple[1])
elif issubclass(pathTuple[1], ClusterAttribute.ClusterAttributeDescriptor):
- attribute = pathTuple[1]
+ return ClusterAttribute.AttributePath.from_attribute(EndpointId=pathTuple[0], Attribute=pathTuple[1])
else:
raise ValueError("Unsupported Attribute Path")
- return ClusterAttribute.AttributePath(
- EndpointId=endpoint, Cluster=cluster, Attribute=attribute)

def _parseDataVersionFilterTuple(self, pathTuple: typing.List[typing.Tuple[int, typing.Type[ClusterObjects.Cluster], int]]):
endpoint = None
@@ -1193,7 +1186,7 @@ class ChipDeviceControllerBase():
else:
raise ValueError("Unsupported Cluster Path")
dataVersion = pathTuple[2]
- return ClusterAttribute.DataVersionFilter(
+ return ClusterAttribute.DataVersionFilter.from_cluster(
EndpointId=endpoint, Cluster=cluster, DataVersion=dataVersion)

def _parseEventPathTuple(self, pathTuple: typing.Union[
@@ -1210,39 +1203,31 @@ class ChipDeviceControllerBase():
typing.Tuple[int,
typing.Type[ClusterObjects.ClusterEvent], int]
]):
- endpoint = None
- cluster = None
- event = None
- urgent = False
if pathTuple in [('*'), ()]:
# Wildcard
- pass
+ return ClusterAttribute.EventPath()
elif not isinstance(pathTuple, tuple):
logging.debug(type(pathTuple))
if isinstance(pathTuple, int):
- endpoint = pathTuple
+ return ClusterAttribute.EventPath(EndpointId=pathTuple)
elif issubclass(pathTuple, ClusterObjects.Cluster):
- cluster = pathTuple
+ return ClusterAttribute.EventPath.from_cluster(EndpointId=None, Cluster=pathTuple)
elif issubclass(pathTuple, ClusterObjects.ClusterEvent):
- event = pathTuple
+ return ClusterAttribute.EventPath.from_event(EndpointId=None, Event=pathTuple)
else:
raise ValueError("Unsupported Event Path")
else:
if pathTuple[0] == '*':
- urgent = pathTuple[-1]
- pass
+ return ClusterAttribute.EventPath(Urgent=pathTuple[-1])
else:
+ urgent = bool(pathTuple[-1]) if len(pathTuple) > 2 else False
# endpoint + (cluster) event / endpoint + cluster
- endpoint = pathTuple[0]
if issubclass(pathTuple[1], ClusterObjects.Cluster):
- cluster = pathTuple[1]
+ return ClusterAttribute.EventPath.from_cluster(EndpointId=pathTuple[0], Cluster=pathTuple[1], Urgent=urgent)
elif issubclass(pathTuple[1], ClusterAttribute.ClusterEvent):
- event = pathTuple[1]
+ return ClusterAttribute.EventPath.from_event(EndpointId=pathTuple[0], Event=pathTuple[1], Urgent=urgent)
else:
raise ValueError("Unsupported Attribute Path")
- urgent = bool(pathTuple[-1]) if len(pathTuple) > 2 else False
- return ClusterAttribute.EventPath(
- EndpointId=endpoint, Cluster=cluster, Event=event, Urgent=urgent)

async def Read(self, nodeid: int, attributes: typing.List[typing.Union[
None, # Empty tuple, all wildcard
@@ -1514,7 +1499,7 @@ class ChipDeviceControllerBase():

result = asyncio.run(self.ReadAttribute(
nodeid, [(endpoint, attributeType)]))
- path = ClusterAttribute.AttributePath(
+ path = ClusterAttribute.AttributePath.from_attribute(
EndpointId=endpoint, Attribute=attributeType)
return im.AttributeReadResult(path=im.AttributePath(nodeId=nodeid, endpointId=path.EndpointId, clusterId=path.ClusterId, attributeId=path.AttributeId),
status=0, value=result[endpoint][clusterType][attributeType], dataVersion=result[endpoint][clusterType][ClusterAttribute.DataVersion])
diff --git a/src/controller/python/chip/clusters/Attribute.py b/src/controller/python/chip/clusters/Attribute.py
index ce522bf452..51389e19a1 100644
--- a/src/controller/python/chip/clusters/Attribute.py
+++ b/src/controller/python/chip/clusters/Attribute.py
@@ -54,62 +54,43 @@ class EventPriority(Enum):
CRITICAL = 2


-@dataclass
+@dataclass(frozen=True)
class AttributePath:
EndpointId: int = None
ClusterId: int = None
AttributeId: int = None

- def __init__(self, EndpointId: int = None, Cluster=None, Attribute=None, ClusterId=None, AttributeId=None):
- self.EndpointId = EndpointId
- if Cluster is not None:
- # Wildcard read for a specific cluster
- if (Attribute is not None) or (ClusterId is not None) or (AttributeId is not None):
- raise Warning(
- "Attribute, ClusterId and AttributeId is ignored when Cluster is specified")
- self.ClusterId = Cluster.id
- return
- if Attribute is not None:
- if (ClusterId is not None) or (AttributeId is not None):
- raise Warning(
- "ClusterId and AttributeId is ignored when Attribute is specified")
- self.ClusterId = Attribute.cluster_id
- self.AttributeId = Attribute.attribute_id
- return
- self.ClusterId = ClusterId
- self.AttributeId = AttributeId
+ @staticmethod
+ def from_cluster(EndpointId: int, Cluster: Cluster) -> AttributePath:
+ if Cluster is None:
+ raise ValueError("Cluster cannot be None")
+ return AttributePath(EndpointId=EndpointId, ClusterId=Cluster.id)
+
+ @staticmethod
+ def from_attribute(EndpointId: int, Attribute: ClusterAttributeDescriptor) -> AttributePath:
+ if Attribute is None:
+ raise ValueError("Attribute cannot be None")
+ return AttributePath(EndpointId=EndpointId, ClusterId=Attribute.cluster_id, AttributeId=Attribute.attribute_id)

def __str__(self) -> str:
return f"{self.EndpointId}/{self.ClusterId}/{self.AttributeId}"

- def __hash__(self):
- return str(self).__hash__()

-
-@dataclass
+@dataclass(frozen=True)
class DataVersionFilter:
EndpointId: int = None
ClusterId: int = None
DataVersion: int = None

- def __init__(self, EndpointId: int = None, Cluster=None, ClusterId=None, DataVersion=None):
- self.EndpointId = EndpointId
- if Cluster is not None:
- # Wildcard read for a specific cluster
- if (ClusterId is not None):
- raise Warning(
- "Attribute, ClusterId and AttributeId is ignored when Cluster is specified")
- self.ClusterId = Cluster.id
- else:
- self.ClusterId = ClusterId
- self.DataVersion = DataVersion
+ @staticmethod
+ def from_cluster(EndpointId: int, Cluster: Cluster, DataVersion: int = None) -> AttributePath:
+ if Cluster is None:
+ raise ValueError("Cluster cannot be None")
+ return DataVersionFilter(EndpointId=EndpointId, ClusterId=Cluster.id, DataVersion=DataVersion)

def __str__(self) -> str:
return f"{self.EndpointId}/{self.ClusterId}/{self.DataVersion}"

- def __hash__(self):
- return str(self).__hash__()
-

@dataclass
class TypedAttributePath:
@@ -165,44 +146,28 @@ class TypedAttributePath:
self.AttributeId = self.AttributeType.attribute_id


-@dataclass
+@dataclass(frozen=True)
class EventPath:
EndpointId: int = None
ClusterId: int = None
EventId: int = None
Urgent: int = None

- def __init__(self, EndpointId: int = None, Cluster=None, Event=None, ClusterId=None, EventId=None, Urgent=None):
- self.EndpointId = EndpointId
- self.Urgent = Urgent
- if Cluster is not None:
- # Wildcard read for a specific cluster
- if (Event is not None) or (ClusterId is not None) or (EventId is not None):
- raise Warning(
- "Event, ClusterId and AttributeId is ignored when Cluster is specified")
- self.ClusterId = Cluster.id
- return
- if Event is not None:
- if (ClusterId is not None) or (EventId is not None):
- raise Warning(
- "ClusterId and EventId is ignored when Event is specified")
- self.ClusterId = Event.cluster_id
- self.EventId = Event.event_id
- return
- self.ClusterId = ClusterId
- self.EventId = EventId
+ @staticmethod
+ def from_cluster(EndpointId: int, Cluster: Cluster, EventId: int = None, Urgent: int = None) -> "EventPath":
+ if Cluster is None:
+ raise ValueError("Cluster cannot be None")
+ return EventPath(EndpointId=EndpointId, ClusterId=Cluster.id, EventId=EventId, Urgent=Urgent)
+
+ @staticmethod
+ def from_event(EndpointId: int, Event: ClusterEvent, Urgent: int = None) -> "EventPath":
+ if Event is None:
+ raise ValueError("Event cannot be None")
+ return EventPath(EndpointId=EndpointId, ClusterId=Event.cluster_id, EventId=Event.event_id, Urgent=Urgent)

def __str__(self) -> str:
return f"{self.EndpointId}/{self.ClusterId}/{self.EventId}/{self.Urgent}"

- def __hash__(self):
- return str(self).__hash__()
-
-
-@dataclass
-class AttributePathWithListIndex(AttributePath):
- ListIndex: int = None
-

@dataclass
class EventHeader:
@@ -711,7 +676,7 @@ class AsyncReadTransaction:
def GetAllEventValues(self):
return self._events

- def handleAttributeData(self, path: AttributePathWithListIndex, dataVersion: int, status: int, data: bytes):
+ def handleAttributeData(self, path: AttributePath, dataVersion: int, status: int, data: bytes):
try:
imStatus = chip.interaction_model.Status(status)

diff --git a/src/controller/python/test/test_scripts/cluster_objects.py b/src/controller/python/test/test_scripts/cluster_objects.py
index 53516c1dc7..37f6819cbe 100644
--- a/src/controller/python/test/test_scripts/cluster_objects.py
+++ b/src/controller/python/test/test_scripts/cluster_objects.py
@@ -164,7 +164,7 @@ class ClusterObjectTests:
]
)
expectedRes = [
- AttributeStatus(Path=AttributePath(
+ AttributeStatus(Path=AttributePath.from_attribute(
EndpointId=1,
Attribute=Clusters.UnitTesting.Attributes.ListLongOctetString), Status=chip.interaction_model.Status.Success),
]
--
2.45.2

2,532 changes: 2,532 additions & 0 deletions 0013-Python-Drop-chip-device-ctrl-33488.patch

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
From 20f1c72293991ad01043660b777e53be0992bae5 Mon Sep 17 00:00:00 2001
Message-ID: <20f1c72293991ad01043660b777e53be0992bae5.1717003814.git.stefan@agner.ch>
From 3997a66e9e436646a4652448bc72309d8afee2bf Mon Sep 17 00:00:00 2001
From: Stefan Agner <stefan@agner.ch>
Date: Wed, 29 May 2024 19:05:43 +0200
Subject: [PATCH] [Python] Remove obsolete callback handling
Date: Thu, 30 May 2024 22:41:35 +0200
Subject: [PATCH] [Python] Remove obsolete callback handling (#33665)

The Call() function currently still has some callback handling code
the completeEvent and callbackRes variables. These are only used when
@@ -16,15 +15,25 @@ However, when calling the SDK from multiple threads, then another Call()
Might accidentally release a call to CallAsyncWithCompleteCallback()
early.
---
src/controller/python/chip/ChipStack.py | 19 -------------------
1 file changed, 19 deletions(-)
src/controller/python/chip/ChipStack.py | 22 +---------------------
1 file changed, 1 insertion(+), 21 deletions(-)

diff --git a/src/controller/python/chip/ChipStack.py b/src/controller/python/chip/ChipStack.py
index 6df7e41de4..a8f07941a2 100644
index 35f9e24ef4..3a167bb6bc 100644
--- a/src/controller/python/chip/ChipStack.py
+++ b/src/controller/python/chip/ChipStack.py
@@ -164,9 +164,6 @@ class AsyncCallableHandle:
return self._res
@@ -32,8 +32,7 @@ import logging
import os
import sys
import time
-from ctypes import (CFUNCTYPE, POINTER, Structure, c_bool, c_char_p, c_int64, c_uint8, c_uint16, c_uint32, c_ulong, c_void_p,
- py_object, pythonapi)
+from ctypes import CFUNCTYPE, Structure, c_bool, c_char_p, c_int64, c_uint8, c_uint16, c_uint32, c_void_p, py_object, pythonapi
from threading import Condition, Event, Lock

import chip.native
@@ -194,9 +193,6 @@ class AsyncioCallableHandle:
pythonapi.Py_DecRef(py_object(self))


-_CompleteFunct = CFUNCTYPE(None, c_void_p, c_void_p)
@@ -33,7 +42,7 @@ index 6df7e41de4..a8f07941a2 100644
_LogMessageFunct = CFUNCTYPE(
None, c_int64, c_int64, c_char_p, c_uint8, c_char_p)
_ChipThreadTaskRunnerFunct = CFUNCTYPE(None, py_object)
@@ -241,21 +238,11 @@ class ChipStack(object):
@@ -272,21 +268,11 @@ class ChipStack(object):
self.logger.addHandler(logHandler)
self.logger.setLevel(logging.DEBUG)

@@ -55,21 +64,22 @@ index 6df7e41de4..a8f07941a2 100644
# set by other modules(BLE) that require service by thread while thread blocks.
self.blockingCB = None

@@ -357,14 +344,8 @@ class ChipStack(object):
@@ -389,15 +375,9 @@ class ChipStack(object):
This function is a wrapper of PostTaskOnChipThread, which includes some handling of application specific logics.
Calling this function on CHIP on CHIP mainloop thread will cause deadlock.
'''
- # throw error if op in progress
- self.callbackRes = None
- self.completeEvent.clear()
# TODO: Lock probably no longer necessary, see https://github.com/project-chip/connectedhomeip/issues/33321.
with self.networkLock:
res = self.PostTaskOnChipThread(callFunct).Wait(timeoutMs)
- self.completeEvent.set()
- if res == 0 and self.callbackRes is not None:
- return self.callbackRes
return res

def CallAsync(self, callFunct):
async def CallAsync(self, callFunct, timeoutMs: int = None):
--
2.45.1
2.45.2

94 changes: 94 additions & 0 deletions 0015-Python-Add-automation-level-to-log-defines-33670.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
From 0fe7d8b3d9c920ce7dc970097962dca14f9d8f03 Mon Sep 17 00:00:00 2001
From: Stefan Agner <stefan@agner.ch>
Date: Fri, 31 May 2024 15:24:42 +0200
Subject: [PATCH] [Python] Add "automation" level to log defines (#33670)

So far the automation log level was missing. Add it to the log level
defines in the logging module.

While at it, also rename to LOG_CATEGORY (instead of ERROR_CATEGORY)
and remove duplicated log level definitions in ChipStack.
---
src/controller/python/chip/ChipStack.py | 16 ++++------------
src/controller/python/chip/logging/__init__.py | 17 +++++++++--------
2 files changed, 13 insertions(+), 20 deletions(-)

diff --git a/src/controller/python/chip/ChipStack.py b/src/controller/python/chip/ChipStack.py
index 3a167bb6bc..beeaedd6ae 100644
--- a/src/controller/python/chip/ChipStack.py
+++ b/src/controller/python/chip/ChipStack.py
@@ -36,6 +36,7 @@ from ctypes import CFUNCTYPE, Structure, c_bool, c_char_p, c_int64, c_uint8, c_u
from threading import Condition, Event, Lock

import chip.native
+from chip.logging import LOG_CATEGORY_AUTOMATION, LOG_CATEGORY_DETAIL, LOG_CATEGORY_ERROR, LOG_CATEGORY_PROGRESS
from chip.native import PyChipError

from .ChipUtility import ChipUtility
@@ -78,23 +79,14 @@ class DeviceStatusStruct(Structure):
class LogCategory(object):
"""Debug logging categories used by chip."""

- # NOTE: These values must correspond to those used in the chip C++ code.
- Disabled = 0
- Error = 1
- Progress = 2
- Detail = 3
- Retain = 4
-
@staticmethod
def categoryToLogLevel(cat):
- if cat == LogCategory.Error:
+ if cat == LOG_CATEGORY_ERROR:
return logging.ERROR
- elif cat == LogCategory.Progress:
+ elif cat == LOG_CATEGORY_PROGRESS:
return logging.INFO
- elif cat == LogCategory.Detail:
+ elif cat in (LOG_CATEGORY_DETAIL, LOG_CATEGORY_AUTOMATION):
return logging.DEBUG
- elif cat == LogCategory.Retain:
- return logging.CRITICAL
else:
return logging.NOTSET

diff --git a/src/controller/python/chip/logging/__init__.py b/src/controller/python/chip/logging/__init__.py
index 047d3f4f8e..aca671997d 100644
--- a/src/controller/python/chip/logging/__init__.py
+++ b/src/controller/python/chip/logging/__init__.py
@@ -19,11 +19,12 @@ import logging
from chip.logging.library_handle import _GetLoggingLibraryHandle
from chip.logging.types import LogRedirectCallback_t

-# Defines match support/logging/Constants.h (LogCategory enum)
-ERROR_CATEGORY_NONE = 0
-ERROR_CATEGORY_ERROR = 1
-ERROR_CATEGORY_PROGRESS = 2
-ERROR_CATEGORY_DETAIL = 3
+# Defines match src/lib/support/logging/Constants.h (LogCategory enum)
+LOG_CATEGORY_NONE = 0
+LOG_CATEGORY_ERROR = 1
+LOG_CATEGORY_PROGRESS = 2
+LOG_CATEGORY_DETAIL = 3
+LOG_CATEGORY_AUTOMATION = 4


@LogRedirectCallback_t
@@ -34,11 +35,11 @@ def _RedirectToPythonLogging(category, module, message):

logger = logging.getLogger('chip.native.%s' % module)

- if category == ERROR_CATEGORY_ERROR:
+ if category == LOG_CATEGORY_ERROR:
logger.error("%s", message)
- elif category == ERROR_CATEGORY_PROGRESS:
+ elif category == LOG_CATEGORY_PROGRESS:
logger.info("%s", message)
- elif category == ERROR_CATEGORY_DETAIL:
+ elif category in (LOG_CATEGORY_DETAIL, LOG_CATEGORY_AUTOMATION):
logger.debug("%s", message)
else:
# All logs are expected to have some reasonable category. This treats
--
2.45.2

256 changes: 256 additions & 0 deletions 0016-Python-Remove-obsolete-logging-callbacks-33718.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
From 92dc4417ed88d016d0b21e377d1c04a97e55f34e Mon Sep 17 00:00:00 2001
From: Stefan Agner <stefan@agner.ch>
Date: Tue, 4 Jun 2024 07:14:58 +0200
Subject: [PATCH] [Python] Remove obsolete logging callbacks (#33718)

Since #5024 there is a new logging callback working. The old code has
partially been removed in #4690, but never completely. Drop the old
logging code for good.
---
.../ChipDeviceController-ScriptBinding.cpp | 12 --
src/controller/python/chip/ChipStack.py | 148 +-----------------
2 files changed, 2 insertions(+), 158 deletions(-)

diff --git a/src/controller/python/ChipDeviceController-ScriptBinding.cpp b/src/controller/python/ChipDeviceController-ScriptBinding.cpp
index a55d3865bd..728fd5801f 100644
--- a/src/controller/python/ChipDeviceController-ScriptBinding.cpp
+++ b/src/controller/python/ChipDeviceController-ScriptBinding.cpp
@@ -212,7 +212,6 @@ PyChipError pychip_DeviceCommissioner_CloseBleConnection(chip::Controller::Devic

const char * pychip_Stack_ErrorToString(ChipError::StorageType err);
const char * pychip_Stack_StatusReportToString(uint32_t profileId, uint16_t statusCode);
-void pychip_Stack_SetLogFunct(LogMessageFunct logFunct);

PyChipError pychip_GetConnectedDeviceByNodeId(chip::Controller::DeviceCommissioner * devCtrl, chip::NodeId nodeId,
chip::Controller::Python::PyObject * context, DeviceAvailableFunc callback);
@@ -863,17 +862,6 @@ uint64_t pychip_GetCommandSenderHandle(chip::DeviceProxy * device)
return 0;
}

-void pychip_Stack_SetLogFunct(LogMessageFunct logFunct)
-{
- // TODO: determine if log redirection is supposed to be functioning in CHIP
- //
- // Background: original log baseline supported 'redirect logs to this
- // function' however CHIP does not currently provide this.
- //
- // Ideally log redirection should work so that python code can do things
- // like using the log module.
-}
-
PyChipError pychip_DeviceController_PostTaskOnChipThread(ChipThreadTaskRunnerFunct callback, void * pythonContext)
{
if (callback == nullptr || pythonContext == nullptr)
diff --git a/src/controller/python/chip/ChipStack.py b/src/controller/python/chip/ChipStack.py
index beeaedd6ae..06afff3ef3 100644
--- a/src/controller/python/chip/ChipStack.py
+++ b/src/controller/python/chip/ChipStack.py
@@ -28,15 +28,11 @@ from __future__ import absolute_import, print_function

import asyncio
import builtins
-import logging
import os
-import sys
-import time
-from ctypes import CFUNCTYPE, Structure, c_bool, c_char_p, c_int64, c_uint8, c_uint16, c_uint32, c_void_p, py_object, pythonapi
+from ctypes import CFUNCTYPE, Structure, c_bool, c_char_p, c_uint16, c_uint32, c_void_p, py_object, pythonapi
from threading import Condition, Event, Lock

import chip.native
-from chip.logging import LOG_CATEGORY_AUTOMATION, LOG_CATEGORY_DETAIL, LOG_CATEGORY_ERROR, LOG_CATEGORY_PROGRESS
from chip.native import PyChipError

from .ChipUtility import ChipUtility
@@ -76,51 +72,6 @@ class DeviceStatusStruct(Structure):
]


-class LogCategory(object):
- """Debug logging categories used by chip."""
-
- @staticmethod
- def categoryToLogLevel(cat):
- if cat == LOG_CATEGORY_ERROR:
- return logging.ERROR
- elif cat == LOG_CATEGORY_PROGRESS:
- return logging.INFO
- elif cat in (LOG_CATEGORY_DETAIL, LOG_CATEGORY_AUTOMATION):
- return logging.DEBUG
- else:
- return logging.NOTSET
-
-
-class ChipLogFormatter(logging.Formatter):
- """A custom logging.Formatter for logging chip library messages."""
-
- def __init__(
- self,
- datefmt=None,
- logModulePrefix=False,
- logLevel=False,
- logTimestamp=False,
- logMSecs=True,
- ):
- fmt = "%(message)s"
- if logModulePrefix:
- fmt = "CHIP:%(chip-module)s: " + fmt
- if logLevel:
- fmt = "%(levelname)s:" + fmt
- if datefmt is not None or logTimestamp:
- fmt = "%(asctime)s " + fmt
- super(ChipLogFormatter, self).__init__(fmt=fmt, datefmt=datefmt)
- self.logMSecs = logMSecs
-
- def formatTime(self, record, datefmt=None):
- if datefmt is None:
- timestampStr = time.strftime("%Y-%m-%d %H:%M:%S%z")
- if self.logMSecs:
- timestampUS = record.__dict__.get("timestamp-usec", 0)
- timestampStr = "%s.%03ld" % (timestampStr, timestampUS / 1000)
- return timestampStr
-
-
class AsyncCallableHandle:
def __init__(self, callback):
self._callback = callback
@@ -185,15 +136,12 @@ class AsyncioCallableHandle:
pythonapi.Py_DecRef(py_object(self))


-_LogMessageFunct = CFUNCTYPE(
- None, c_int64, c_int64, c_char_p, c_uint8, c_char_p)
_ChipThreadTaskRunnerFunct = CFUNCTYPE(None, py_object)


@_singleton
class ChipStack(object):
- def __init__(self, persistentStoragePath: str, installDefaultLogHandler=True,
- bluetoothAdapter=None, enableServerInteractions=True):
+ def __init__(self, persistentStoragePath: str, enableServerInteractions=True):
builtins.enableDebugMode = False

# TODO: Probably no longer necessary, see https://github.com/project-chip/connectedhomeip/issues/33321.
@@ -206,8 +154,6 @@ class ChipStack(object):
self.callbackRes = None
self.commissioningEventRes = None
self.openCommissioningWindowPincode = {}
- self._activeLogFunct = None
- self.addModulePrefixToLogMessage = True
self._enableServerInteractions = enableServerInteractions

#
@@ -216,50 +162,6 @@ class ChipStack(object):
#
self._loadLib()

- # Arrange to log output from the chip library to a python logger object with the
- # name 'chip.ChipStack'. If desired, applications can override this behavior by
- # setting self.logger to a different python logger object, or by calling setLogFunct()
- # with their own logging function.
- self.logger = logging.getLogger(__name__)
- self.setLogFunct(self.defaultLogFunct)
-
- # Determine if there are already handlers installed for the logger. Python 3.5+
- # has a method for this; on older versions the check has to be done manually.
- if hasattr(self.logger, "hasHandlers"):
- hasHandlers = self.logger.hasHandlers()
- else:
- hasHandlers = False
- logger = self.logger
- while logger is not None:
- if len(logger.handlers) > 0:
- hasHandlers = True
- break
- if not logger.propagate:
- break
- logger = logger.parent
-
- # If a logging handler has not already been initialized for 'chip.ChipStack',
- # or any one of its parent loggers, automatically configure a handler to log to
- # stdout. This maintains compatibility with a number of applications which expect
- # chip log output to go to stdout by default.
- #
- # This behavior can be overridden in a variety of ways:
- # - Initialize a different log handler before ChipStack is initialized.
- # - Pass installDefaultLogHandler=False when initializing ChipStack.
- # - Replace the StreamHandler on self.logger with a different handler object.
- # - Set a different Formatter object on the existing StreamHandler object.
- # - Reconfigure the existing ChipLogFormatter object.
- # - Configure chip to call an application-specific logging function by
- # calling self.setLogFunct().
- # - Call self.setLogFunct(None), which will configure the chip library
- # to log directly to stdout, bypassing python altogether.
- #
- if installDefaultLogHandler and not hasHandlers:
- logHandler = logging.StreamHandler(stream=sys.stdout)
- logHandler.setFormatter(ChipLogFormatter())
- self.logger.addHandler(logHandler)
- self.logger.setLevel(logging.DEBUG)
-
@_ChipThreadTaskRunnerFunct
def HandleChipThreadRun(callback):
callback()
@@ -292,49 +194,6 @@ class ChipStack(object):
def enableServerInteractions(self):
return self._enableServerInteractions

- @property
- def defaultLogFunct(self):
- """Returns a python callable which, when called, logs a message to the python logger object
- currently associated with the ChipStack object.
- The returned function is suitable for passing to the setLogFunct() method."""
-
- def logFunct(timestamp, timestampUSec, moduleName, logCat, message):
- moduleName = ChipUtility.CStringToString(moduleName)
- message = ChipUtility.CStringToString(message)
- if self.addModulePrefixToLogMessage:
- message = "CHIP:%s: %s" % (moduleName, message)
- logLevel = LogCategory.categoryToLogLevel(logCat)
- msgAttrs = {
- "chip-module": moduleName,
- "timestamp": timestamp,
- "timestamp-usec": timestampUSec,
- }
- self.logger.log(logLevel, message, extra=msgAttrs)
-
- return logFunct
-
- def setLogFunct(self, logFunct):
- """Set the function used by the chip library to log messages.
- The supplied object must be a python callable that accepts the following
- arguments:
- timestamp (integer)
- timestampUS (integer)
- module name (encoded UTF-8 string)
- log category (integer)
- message (encoded UTF-8 string)
- Specifying None configures the chip library to log directly to stdout."""
- if logFunct is None:
- logFunct = 0
- if not isinstance(logFunct, _LogMessageFunct):
- logFunct = _LogMessageFunct(logFunct)
- # TODO: Lock probably no longer necessary, see https://github.com/project-chip/connectedhomeip/issues/33321.
- with self.networkLock:
- # NOTE: ChipStack must hold a reference to the CFUNCTYPE object while it is
- # set. Otherwise it may get garbage collected, and logging calls from the
- # chip library will fail.
- self._activeLogFunct = logFunct
- self._ChipStackLib.pychip_Stack_SetLogFunct(logFunct)
-
def Shutdown(self):
#
# Terminate Matter thread and shutdown the stack.
@@ -484,9 +343,6 @@ class ChipStack(object):
self._ChipStackLib.pychip_Stack_StatusReportToString.restype = c_char_p
self._ChipStackLib.pychip_Stack_ErrorToString.argtypes = [c_uint32]
self._ChipStackLib.pychip_Stack_ErrorToString.restype = c_char_p
- self._ChipStackLib.pychip_Stack_SetLogFunct.argtypes = [
- _LogMessageFunct]
- self._ChipStackLib.pychip_Stack_SetLogFunct.restype = PyChipError

self._ChipStackLib.pychip_DeviceController_PostTaskOnChipThread.argtypes = [
_ChipThreadTaskRunnerFunct, py_object]
--
2.45.2

63 changes: 63 additions & 0 deletions 0017-Python-Drop-network-lock-33720.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
From 025bdb7a1e9b9669ab091b96f9ac0adefe3a53dd Mon Sep 17 00:00:00 2001
From: Stefan Agner <stefan@agner.ch>
Date: Wed, 5 Jun 2024 16:06:15 +0200
Subject: [PATCH] [Python] Drop network lock (#33720)

The network lock is not needed in the Python controller, as all calls
to the SDK are made by posting to the Matter SDK event loop through
ScheduleWork(), hence are guaranteed to be serialized.

From how I understand ScheduleWork() works, it pushes the work to the
event loop through PostEvent() which at least on POSIX is using the
thread safe device queue (see GenericPlatformManagerImpl_POSIX.cpp).
---
src/controller/python/chip/ChipStack.py | 12 ++----------
1 file changed, 2 insertions(+), 10 deletions(-)

diff --git a/src/controller/python/chip/ChipStack.py b/src/controller/python/chip/ChipStack.py
index 06afff3ef3..b47c463982 100644
--- a/src/controller/python/chip/ChipStack.py
+++ b/src/controller/python/chip/ChipStack.py
@@ -144,8 +144,6 @@ class ChipStack(object):
def __init__(self, persistentStoragePath: str, enableServerInteractions=True):
builtins.enableDebugMode = False

- # TODO: Probably no longer necessary, see https://github.com/project-chip/connectedhomeip/issues/33321.
- self.networkLock = Lock()
self.completeEvent = Event()
self.commissioningCompleteEvent = Event()
self._ChipStackLib = None
@@ -212,7 +210,6 @@ class ChipStack(object):
# #20437 tracks consolidating these.
#
self._ChipStackLib.pychip_CommonStackShutdown()
- self.networkLock = None
self.completeEvent = None
self._ChipStackLib = None
self._chipDLLPath = None
@@ -226,10 +223,7 @@ class ChipStack(object):
This function is a wrapper of PostTaskOnChipThread, which includes some handling of application specific logics.
Calling this function on CHIP on CHIP mainloop thread will cause deadlock.
'''
- # TODO: Lock probably no longer necessary, see https://github.com/project-chip/connectedhomeip/issues/33321.
- with self.networkLock:
- res = self.PostTaskOnChipThread(callFunct).Wait(timeoutMs)
- return res
+ return self.PostTaskOnChipThread(callFunct).Wait(timeoutMs)

async def CallAsync(self, callFunct, timeoutMs: int = None):
'''Run a Python function on CHIP stack, and wait for the response.
@@ -256,9 +250,7 @@ class ChipStack(object):
# throw error if op in progress
self.callbackRes = None
self.completeEvent.clear()
- # TODO: Lock probably no longer necessary, see https://github.com/project-chip/connectedhomeip/issues/33321.
- with self.networkLock:
- res = self.PostTaskOnChipThread(callFunct).Wait()
+ res = self.PostTaskOnChipThread(callFunct).Wait()

if not res.is_success:
self.completeEvent.set()
--
2.45.2

95 changes: 95 additions & 0 deletions 0018-Python-Remove-Python-Bluetooth-and-ChipStack-event-l.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
From a4fb58131d9023ad3a3ec01d44c5ae87946d6b59 Mon Sep 17 00:00:00 2001
From: Stefan Agner <stefan@agner.ch>
Date: Thu, 6 Jun 2024 17:11:34 +0200
Subject: [PATCH] [Python] Remove Python Bluetooth and ChipStack event loop
integration (#33775)

The Python Bluetooth implementation for Linux (`BluezManager` in
ChipBluezMgr.py) and macOS (`CoreBluetoothManager` in
ChipCoreBluetoothMgr.py) integrate with ChipStack to pump their event
loops on long running operations such as commissioning (through
`CallAsyncWithCompleteCallback()`). From what I can tell, the Python
Bluetooth stack is only used for some mbed integration tests.
Specifically through `scan_chip_ble_devices()`
in src/test_driver/mbed/integration_tests/common/utils.py. This
operation doesn't need the event loop integration.

So as a first step, this PR simply breaks this tie and removes the
event loop integration with the Device ChipStack/ChipDeviceController.
---
src/controller/python/chip/ChipBluezMgr.py | 1 -
src/controller/python/chip/ChipCoreBluetoothMgr.py | 2 --
src/controller/python/chip/ChipDeviceCtrl.py | 5 -----
src/controller/python/chip/ChipStack.py | 8 +-------
4 files changed, 1 insertion(+), 15 deletions(-)

diff --git a/src/controller/python/chip/ChipBluezMgr.py b/src/controller/python/chip/ChipBluezMgr.py
index e480750b60..bacf383710 100644
--- a/src/controller/python/chip/ChipBluezMgr.py
+++ b/src/controller/python/chip/ChipBluezMgr.py
@@ -807,7 +807,6 @@ class BluezManager(ChipBleBase):
self.rx = None
self.setInputHook(self.readlineCB)
self.devMgr = devMgr
- self.devMgr.SetBlockingCB(self.devMgrCB)

def __del__(self):
self.disconnect()
diff --git a/src/controller/python/chip/ChipCoreBluetoothMgr.py b/src/controller/python/chip/ChipCoreBluetoothMgr.py
index 3f792a5a4d..4a65f1e237 100644
--- a/src/controller/python/chip/ChipCoreBluetoothMgr.py
+++ b/src/controller/python/chip/ChipCoreBluetoothMgr.py
@@ -184,8 +184,6 @@ class CoreBluetoothManager(ChipBleBase):
def __del__(self):
self.disconnect()
self.setInputHook(self.orig_input_hook)
- self.devCtrl.SetBlockingCB(None)
- self.devCtrl.SetBleEventCB(None)

def devMgrCB(self):
"""A callback used by ChipDeviceCtrl.py to drive the OSX runloop while the
diff --git a/src/controller/python/chip/ChipDeviceCtrl.py b/src/controller/python/chip/ChipDeviceCtrl.py
index 1d1627c46d..acbbc88b3e 100644
--- a/src/controller/python/chip/ChipDeviceCtrl.py
+++ b/src/controller/python/chip/ChipDeviceCtrl.py
@@ -1461,11 +1461,6 @@ class ChipDeviceControllerBase():
else:
return res.events

- def SetBlockingCB(self, blockingCB):
- self.CheckIsActive()
-
- self._ChipStack.blockingCB = blockingCB
-
def SetIpk(self, ipk: bytes):
self._ChipStack.Call(
lambda: self._dmLib.pychip_DeviceController_SetIpk(self.devCtrl, ipk, len(ipk))
diff --git a/src/controller/python/chip/ChipStack.py b/src/controller/python/chip/ChipStack.py
index b47c463982..5fd0601ba2 100644
--- a/src/controller/python/chip/ChipStack.py
+++ b/src/controller/python/chip/ChipStack.py
@@ -165,8 +165,6 @@ class ChipStack(object):
callback()

self.cbHandleChipThreadRun = HandleChipThreadRun
- # set by other modules(BLE) that require service by thread while thread blocks.
- self.blockingCB = None

#
# Storage has to be initialized BEFORE initializing the stack, since the latter
@@ -255,11 +253,7 @@ class ChipStack(object):
if not res.is_success:
self.completeEvent.set()
raise res.to_exception()
- while not self.completeEvent.isSet():
- if self.blockingCB:
- self.blockingCB()
-
- self.completeEvent.wait(0.05)
+ self.completeEvent.wait()
if isinstance(self.callbackRes, ChipStackException):
raise self.callbackRes
return self.callbackRes
--
2.45.2

54 changes: 54 additions & 0 deletions 0019-Python-Add-TriggerResubscribeIfScheduled-to-Subscrip.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
From 9e3eeeaf21a3a258ea6d068f8ade8c321b7b5563 Mon Sep 17 00:00:00 2001
From: Stefan Agner <stefan@agner.ch>
Date: Fri, 7 Jun 2024 15:50:34 +0200
Subject: [PATCH] [Python] Add TriggerResubscribeIfScheduled to
SubscriptionTransaction (#33774)

Add TriggerResubscribeIfScheduled to SubscriptionTransaction. If the
ReadClient currently has a resubscription attempt scheduled, This
function allows to trigger that attempt immediately. This is useful
when the server side is up and communicating, and it's a good time to
try to resubscribe.
---
src/controller/python/chip/clusters/Attribute.py | 7 +++++++
src/controller/python/chip/clusters/attribute.cpp | 6 ++++++
2 files changed, 13 insertions(+)

diff --git a/src/controller/python/chip/clusters/Attribute.py b/src/controller/python/chip/clusters/Attribute.py
index 51389e19a1..838936e83b 100644
--- a/src/controller/python/chip/clusters/Attribute.py
+++ b/src/controller/python/chip/clusters/Attribute.py
@@ -478,6 +478,13 @@ class SubscriptionTransaction:
lambda: handle.pychip_ReadClient_OverrideLivenessTimeout(self._readTransaction._pReadClient, timeoutMs)
)

+ async def TriggerResubscribeIfScheduled(self, reason: str):
+ handle = chip.native.GetLibraryHandle()
+ await builtins.chipStack.CallAsync(
+ lambda: handle.pychip_ReadClient_TriggerResubscribeIfScheduled(
+ self._readTransaction._pReadClient, reason.encode("utf-8"))
+ )
+
def GetReportingIntervalsSeconds(self) -> Tuple[int, int]:
'''
Retrieve the reporting intervals associated with an active subscription.
diff --git a/src/controller/python/chip/clusters/attribute.cpp b/src/controller/python/chip/clusters/attribute.cpp
index b73b4a49b4..7c5b2c906a 100644
--- a/src/controller/python/chip/clusters/attribute.cpp
+++ b/src/controller/python/chip/clusters/attribute.cpp
@@ -464,6 +464,12 @@ void pychip_ReadClient_OverrideLivenessTimeout(ReadClient * pReadClient, uint32_
pReadClient->OverrideLivenessTimeout(System::Clock::Milliseconds32(livenessTimeoutMs));
}

+void pychip_ReadClient_TriggerResubscribeIfScheduled(ReadClient * pReadClient, const char * reason)
+{
+ VerifyOrDie(pReadClient != nullptr);
+ pReadClient->TriggerResubscribeIfScheduled(reason);
+}
+
PyChipError pychip_ReadClient_GetReportingIntervals(ReadClient * pReadClient, uint16_t * minIntervalSec, uint16_t * maxIntervalSec)
{
VerifyOrDie(pReadClient != nullptr);
--
2.45.2