2 # Copyright (c) 2020-2021 Project CHIP Authors
3 # Copyright (c) 2019-2020 Google, LLC.
4 # Copyright (c) 2013-2018 Nest Labs, Inc.
7 # Licensed under the Apache License, Version 2.0 (the "License");
8 # you may not use this file except in compliance with the License.
9 # You may obtain a copy of the License at
11 # http://www.apache.org/licenses/LICENSE-2.0
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS,
15 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 # See the License for the specific language governing permissions and
17 # limitations under the License.
22 # Python interface for Chip Device Manager
25 """Chip Device Controller interface
28 from __future__ import absolute_import
29 from __future__ import print_function
31 from threading import Thread
33 from .ChipStack import *
34 from .clusters.CHIPClusters import *
38 __all__ = ["ChipDeviceController"]
40 _DevicePairingDelegate_OnPairingCompleteFunct = CFUNCTYPE(None, c_uint32)
41 _DeviceAddressUpdateDelegate_OnUpdateComplete = CFUNCTYPE(None, c_uint64, c_uint32)
43 # This is a fix for WEAV-429. Jay Logue recommends revisiting this at a later
44 # date to allow for truely multiple instances so this is temporary.
48 def wrapper(*args, **kwargs):
49 if instance[0] is None:
50 instance[0] = cls(*args, **kwargs)
56 class DCState(enum.IntEnum):
60 RENDEZVOUS_ONGOING = 3
61 RENDEZVOUS_CONNECTED = 4
64 class ChipDeviceController(object):
65 def __init__(self, startNetworkThread=True, controllerNodeId=0, bluetoothAdapter=0):
66 self.state = DCState.NOT_INITIALIZED
68 self._ChipStack = ChipStack(bluetoothAdapter=bluetoothAdapter)
73 devCtrl = c_void_p(None)
74 addressUpdater = c_void_p(None)
75 res = self._dmLib.pychip_DeviceController_NewDeviceController(pointer(devCtrl), controllerNodeId)
77 raise self._ChipStack.ErrorToException(res)
79 res = self._dmLib.pychip_DeviceAddressUpdater_New(pointer(addressUpdater), devCtrl)
81 raise self._ChipStack.ErrorToException(res)
83 self.devCtrl = devCtrl
84 self.addressUpdater = addressUpdater
85 self._ChipStack.devCtrl = devCtrl
87 self._Cluster = ChipClusters(self._ChipStack)
88 self._Cluster.InitLib(self._dmLib)
90 def HandleKeyExchangeComplete(err):
92 print("Failed to establish secure session to device: {}".format(err))
93 self._ChipStack.callbackRes = False
95 print("Secure Session to Device Established")
96 self._ChipStack.callbackRes = True
97 self.state = DCState.IDLE
98 self._ChipStack.completeEvent.set()
100 def HandleAddressUpdateComplete(nodeid, err):
102 print("Failed to update node address: {}".format(err))
104 print("Node address has been updated")
105 self.state = DCState.IDLE
106 self._ChipStack.callbackRes = err
107 self._ChipStack.completeEvent.set()
109 self.cbHandleKeyExchangeCompleteFunct = _DevicePairingDelegate_OnPairingCompleteFunct(HandleKeyExchangeComplete)
110 self._dmLib.pychip_ScriptDevicePairingDelegate_SetKeyExchangeCallback(self.devCtrl, self.cbHandleKeyExchangeCompleteFunct)
112 self.cbOnAddressUpdateComplete = _DeviceAddressUpdateDelegate_OnUpdateComplete(HandleAddressUpdateComplete)
113 self._dmLib.pychip_ScriptDeviceAddressUpdateDelegate_SetOnAddressUpdateComplete(self.cbOnAddressUpdateComplete)
115 self.state = DCState.IDLE
118 if self.devCtrl != None:
119 self._dmLib.pychip_DeviceAddressUpdater_Delete(self.addressUpdater)
120 self._dmLib.pychip_DeviceController_DeleteDeviceController(self.devCtrl)
123 def IsConnected(self):
124 return self._ChipStack.Call(
125 lambda: self._dmLib.pychip_DeviceController_IsConnected(self.devCtrl)
128 def ConnectBle(self, bleConnection):
129 self._ChipStack.CallAsync(
130 lambda: self._dmLib.pychip_DeviceController_ValidateBTP(
133 self._ChipStack.cbHandleComplete,
134 self._ChipStack.cbHandleError,
138 def ConnectBLE(self, discriminator, setupPinCode, nodeid):
139 self.state = DCState.RENDEZVOUS_ONGOING
140 return self._ChipStack.CallAsync(
141 lambda: self._dmLib.pychip_DeviceController_ConnectBLE(self.devCtrl, discriminator, setupPinCode, nodeid)
144 def ConnectIP(self, ipaddr, setupPinCode, nodeid):
145 self.state = DCState.RENDEZVOUS_ONGOING
146 return self._ChipStack.CallAsync(
147 lambda: self._dmLib.pychip_DeviceController_ConnectIP(self.devCtrl, ipaddr, setupPinCode, nodeid)
150 def ResolveNode(self, fabricid, nodeid):
151 return self._ChipStack.CallAsync(
152 lambda: self._dmLib.pychip_Resolver_ResolveNode(fabricid, nodeid)
155 def GetAddressAndPort(self, nodeid):
156 address = create_string_buffer(64)
159 error = self._ChipStack.Call(
160 lambda: self._dmLib.pychip_DeviceController_GetAddressAndPort(self.devCtrl, nodeid, address, 64, pointer(port))
163 return (address.value.decode(), port.value) if error == 0 else None
165 def ZCLSend(self, cluster, command, nodeid, endpoint, groupid, args):
166 device = c_void_p(None)
167 self._ChipStack.Call(
168 lambda: self._dmLib.pychip_GetDeviceByNodeId(self.devCtrl, nodeid, pointer(device))
171 self._Cluster.SendCommand(device, cluster, command, endpoint, groupid, args)
173 def ZCLReadAttribute(self, cluster, attribute, nodeid, endpoint, groupid):
174 device = c_void_p(None)
175 self._ChipStack.Call(
176 lambda: self._dmLib.pychip_GetDeviceByNodeId(self.devCtrl, nodeid, pointer(device))
179 self._Cluster.ReadAttribute(device, cluster, attribute, endpoint, groupid)
181 def ZCLCommandList(self):
182 return self._Cluster.ListClusterCommands()
184 def ZCLAttributeList(self):
185 return self._Cluster.ListClusterAttributes()
187 def SetLogFilter(self, category):
188 if category < 0 or category > pow(2, 8):
189 raise ValueError("category must be an unsigned 8-bit integer")
191 self._ChipStack.Call(
192 lambda: self._dmLib.pychip_DeviceController_SetLogFilter(category)
195 def GetLogFilter(self):
196 self._ChipStack.Call(
197 lambda: self._dmLib.pychip_DeviceController_GetLogFilter()
200 def SetBlockingCB(self, blockingCB):
201 self._ChipStack.blockingCB = blockingCB
203 def SetWifiCredential(self, ssid, password):
204 ret = self._dmLib.pychip_ScriptDevicePairingDelegate_SetWifiCredential(self.devCtrl, ssid.encode("utf-8") + b'\0', password.encode("utf-8") + b'\0')
206 raise self._ChipStack.ErrorToException(res)
208 def SetThreadCredential(self, channel, panid, masterKey):
209 ret = self._dmLib.pychip_ScriptDevicePairingDelegate_SetThreadCredential(self.devCtrl, channel, panid, masterKey.encode("utf-8") + b'\0')
211 raise self._ChipStack.ErrorToException(ret)
213 # ----- Private Members -----
215 if self._dmLib is None:
216 self._dmLib = CDLL(self._ChipStack.LocateChipDLL())
218 self._dmLib.pychip_DeviceController_NewDeviceController.argtypes = [POINTER(c_void_p), c_uint64]
219 self._dmLib.pychip_DeviceController_NewDeviceController.restype = c_uint32
221 self._dmLib.pychip_DeviceController_DeleteDeviceController.argtypes = [c_void_p]
222 self._dmLib.pychip_DeviceController_DeleteDeviceController.restype = c_uint32
224 self._dmLib.pychip_DeviceController_ConnectBLE.argtypes = [c_void_p, c_uint16, c_uint32, c_uint64]
225 self._dmLib.pychip_DeviceController_ConnectBLE.restype = c_uint32
227 self._dmLib.pychip_DeviceController_ConnectIP.argtypes = [c_void_p, c_char_p, c_uint32, c_uint64]
228 self._dmLib.pychip_DeviceController_ConnectIP.restype = c_uint32
230 self._dmLib.pychip_DeviceController_GetAddressAndPort.argtypes = [c_void_p, c_uint64, c_char_p, c_uint64, POINTER(c_uint16)]
231 self._dmLib.pychip_DeviceController_GetAddressAndPort.restype = c_uint32
233 self._dmLib.pychip_ScriptDevicePairingDelegate_SetWifiCredential.argtypes = [c_void_p, c_char_p, c_char_p]
234 self._dmLib.pychip_ScriptDevicePairingDelegate_SetWifiCredential.restype = c_uint32
236 self._dmLib.pychip_ScriptDevicePairingDelegate_SetKeyExchangeCallback.argtypes = [c_void_p, _DevicePairingDelegate_OnPairingCompleteFunct]
237 self._dmLib.pychip_ScriptDevicePairingDelegate_SetKeyExchangeCallback.restype = c_uint32
239 self._dmLib.pychip_DeviceAddressUpdater_New.argtypes = [POINTER(c_void_p), c_void_p]
240 self._dmLib.pychip_DeviceAddressUpdater_New.restype = c_uint32
242 self._dmLib.pychip_DeviceAddressUpdater_Delete.argtypes = [c_void_p]
243 self._dmLib.pychip_DeviceAddressUpdater_Delete.restype = None
245 self._dmLib.pychip_ScriptDeviceAddressUpdateDelegate_SetOnAddressUpdateComplete.argtypes = [_DeviceAddressUpdateDelegate_OnUpdateComplete]
246 self._dmLib.pychip_ScriptDeviceAddressUpdateDelegate_SetOnAddressUpdateComplete.restype = None
248 self._dmLib.pychip_Resolver_ResolveNode.argtypes = [c_uint64, c_uint64]
249 self._dmLib.pychip_Resolver_ResolveNode.restype = c_uint32
251 self._dmLib.pychip_GetDeviceByNodeId.argtypes = [c_void_p, c_uint64, POINTER(c_void_p)]
252 self._dmLib.pychip_GetDeviceByNodeId.restype = c_uint32