Skip to content

Commit

Permalink
[python] Add ChipStack.RunOnChipThread to run a python function on CH…
Browse files Browse the repository at this point in the history
…IP stack (project-chip#8472)
  • Loading branch information
erjiaqing authored and Nikita committed Sep 23, 2021
1 parent 58f9f38 commit 70115bf
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 8 deletions.
17 changes: 17 additions & 0 deletions src/controller/python/ChipDeviceController-ScriptBinding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
#include <controller/ExampleOperationalCredentialsIssuer.h>
#include <inet/IPAddress.h>
#include <mdns/Resolver.h>
#include <platform/CHIPDeviceLayer.h>
#include <setup_payload/QRCodeSetupPayloadParser.h>
#include <support/BytesToHex.h>
#include <support/CHIPMem.h>
Expand All @@ -62,11 +63,13 @@
using namespace chip;
using namespace chip::Ble;
using namespace chip::Controller;
using namespace chip::DeviceLayer;

extern "C" {
typedef void (*ConstructBytesArrayFunct)(const uint8_t * dataBuf, uint32_t dataLen);
typedef void (*LogMessageFunct)(uint64_t time, uint64_t timeUS, const char * moduleName, uint8_t category, const char * msg);
typedef void (*DeviceAvailableFunc)(Device * device, CHIP_ERROR err);
typedef void (*ChipThreadTaskRunnerFunct)(intptr_t context);
}

namespace {
Expand Down Expand Up @@ -110,6 +113,8 @@ CHIP_ERROR pychip_DeviceController_DiscoverCommissionableNodesDeviceType(chip::C
uint16_t device_type);
CHIP_ERROR pychip_DeviceController_DiscoverCommissionableNodesCommissioningEnabled(chip::Controller::DeviceCommissioner * devCtrl,
uint16_t enabled);
CHIP_ERROR pychip_DeviceController_PostTaskOnChipThread(ChipThreadTaskRunnerFunct callback, void * pythonContext);

CHIP_ERROR
pychip_DeviceController_DiscoverCommissionableNodesCommissioningEnabledFromCommand(chip::Controller::DeviceCommissioner * devCtrl);

Expand Down Expand Up @@ -494,3 +499,15 @@ void pychip_Stack_SetLogFunct(LogMessageFunct logFunct)
// Ideally log redirection should work so that python code can do things
// like using the log module.
}

CHIP_ERROR pychip_DeviceController_PostTaskOnChipThread(ChipThreadTaskRunnerFunct callback, void * pythonContext)
{
if (callback == nullptr || pythonContext == nullptr)
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
// This function is not called and should not be called on CHIP thread, thus we need to acquire a lock for posting tasks.
StackLock lock;
PlatformMgr().ScheduleWork(callback, reinterpret_cast<intptr_t>(pythonContext));
return CHIP_NO_ERROR;
}
6 changes: 2 additions & 4 deletions src/controller/python/chip/ChipDeviceCtrl.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,8 +281,7 @@ def ZCLSend(self, cluster, command, nodeid, endpoint, groupid, args, blocking=Fa
if res != 0:
raise self._ChipStack.ErrorToException(res)
im.ClearCommandStatus(im.PLACEHOLDER_COMMAND_HANDLE)
self._Cluster.SendCommand(
device, cluster, command, endpoint, groupid, args, True)
self._Cluster.SendCommand(device, cluster, command, endpoint, groupid, args, True)
if blocking:
# We only send 1 command by this function, so index is always 0
return im.WaitCommandIndexStatus(im.PLACEHOLDER_COMMAND_HANDLE, 1)
Expand All @@ -298,8 +297,7 @@ def ZCLReadAttribute(self, cluster, attribute, nodeid, endpoint, groupid, blocki
raise self._ChipStack.ErrorToException(res)

# We are not using IM for Attributes.
res = self._Cluster.ReadAttribute(
device, cluster, attribute, endpoint, groupid, False)
res = self._Cluster.ReadAttribute(device, cluster, attribute, endpoint, groupid, False)
if blocking:
return im.GetAttributeReadResponse(im.DEFAULT_ATTRIBUTEREAD_APPID)

Expand Down
63 changes: 59 additions & 4 deletions src/controller/python/chip/ChipStack.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
import glob
import platform
import logging
from threading import Lock, Event
from threading import Lock, Event, Condition
from ctypes import *
from .ChipUtility import ChipUtility
from .exceptions import *
Expand All @@ -47,7 +47,6 @@

ChipStackDLLBaseName = "_ChipDeviceCtrl.so"


def _singleton(cls):
instance = [None]

Expand Down Expand Up @@ -120,10 +119,37 @@ def formatTime(self, record, datefmt=None):
timestampStr = "%s.%03ld" % (timestampStr, timestampUS / 1000)
return timestampStr

class AsyncCallableHandle:
def __init__(self, callback):
self._callback = callback
self._res = None
self._exc = None
self._finish = False
self._cv_lock = Lock()
self._cv = Condition(self._cv_lock)

def __call__(self):
with self._cv_lock:
try:
self._res = self._callback()
except Exception as ex:
self._exc = ex
self._finish = True
self._cv.notify_all()
pythonapi.Py_DecRef(py_object(self))

def Wait(self):
with self._cv:
while self._finish is False:
self._cv.wait()
if self._exc is not None:
raise self._exc
return self._res

_CompleteFunct = CFUNCTYPE(None, c_void_p, c_void_p)
_ErrorFunct = CFUNCTYPE(None, c_void_p, c_void_p, c_ulong, POINTER(DeviceStatusStruct))
_LogMessageFunct = CFUNCTYPE(None, c_int64, c_int64, c_char_p, c_uint8, c_char_p)
_ChipThreadTaskRunnerFunct = CFUNCTYPE(None, py_object)


@_singleton
Expand Down Expand Up @@ -193,6 +219,11 @@ def HandleError(appState, reqState, err, devStatusPtr):
self.callbackRes = self.ErrorToException(err, devStatusPtr)
self.completeEvent.set()

@_ChipThreadTaskRunnerFunct
def HandleChipThreadRun(callback):
callback()

self.cbHandleChipThreadRun = HandleChipThreadRun
self.cbHandleComplete = _CompleteFunct(HandleComplete)
self.cbHandleError = _ErrorFunct(HandleError)
self.blockingCB = None # set by other modules(BLE) that require service by thread while thread blocks.
Expand Down Expand Up @@ -258,22 +289,30 @@ def Shutdown(self):
self.callbackRes = None

def Call(self, callFunct):
'''Run a Python function on CHIP stack, and wait for the response.
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()
with self.networkLock:
res = callFunct()
res = self.PostTaskOnChipThread(callFunct).Wait()
self.completeEvent.set()
if res == 0 and self.callbackRes != None:
return self.callbackRes
return res

def CallAsync(self, callFunct):
'''Run a Python function on CHIP stack, and wait for the application specific response.
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()
with self.networkLock:
res = callFunct()
res = self.PostTaskOnChipThread(callFunct).Wait()

if res != 0:
self.completeEvent.set()
Expand All @@ -287,6 +326,19 @@ def CallAsync(self, callFunct):
raise self.callbackRes
return self.callbackRes

def PostTaskOnChipThread(self, callFunct) -> AsyncCallableHandle:
'''Run a Python function on CHIP stack, and wait for the response.
This function will post a task on CHIP mainloop, and return an object with Wait() method for getting the result.
Calling Wait inside main loop will cause deadlock.
'''
callObj = AsyncCallableHandle(callFunct)
pythonapi.Py_IncRef(py_object(callObj))
res = self._ChipStackLib.pychip_DeviceController_PostTaskOnChipThread(self.cbHandleChipThreadRun, py_object(callObj))
if res != 0:
pythonapi.Py_DecRef(py_object(callObj))
raise self.ErrorToException(res)
return callObj

def ErrorToException(self, err, devStatusPtr=None):
if err == 4044 and devStatusPtr:
devStatus = devStatusPtr.contents
Expand Down Expand Up @@ -379,3 +431,6 @@ def _loadLib(self):

self._ChipStackLib.pychip_BLEMgrImpl_ConfigureBle.argtypes = [c_uint32]
self._ChipStackLib.pychip_BLEMgrImpl_ConfigureBle.restype = c_uint32

self._ChipStackLib.pychip_DeviceController_PostTaskOnChipThread.argtypes = [_ChipThreadTaskRunnerFunct, py_object]
self._ChipStackLib.pychip_DeviceController_PostTaskOnChipThread.restype = c_uint32

0 comments on commit 70115bf

Please sign in to comment.