1# -*- coding: utf-8 -*- 2# Copyright 2019 Oticon A/S 3# SPDX-License-Identifier: Apache-2.0 4 5from enum import IntEnum; 6from components.utils import *; 7from components.basic_commands import *; 8from components.address import *; 9from components.events import *; 10 11class InitiatorFilterPolicy(IntEnum): 12 FILTER_NONE = 0 # Filter Accept list is not used to determine which advertiser to connect to. Peer_Address_Type and Peer_Address shall be used. 13 FILTER_ACCEPT_LIST_ONLY = 1 # Filter Accept list is used to determine which advertiser to connect to. Peer_Address_Type and Peer_Address shall be ignored. 14 15class Initiator: 16 """ 17 A Initiator can create and destroy connections. 18 - Set parameters for connection creation 19 - Monitor connection status 20 - Terminate connection 21 """ 22 """ 23 Constructor: 24 transport - PTTT_nwtsim object 25 initiator - Number; Device identifier 26 peer - Number; Device identifier 27 trace - Trace object 28 initiatorAddress - ExtendedAddressType enum holding the address of the initiator (only the address type is used) 29 peerAddress - IdentityAddressType enum holding the address of the peer (both type and address are used) 30 filterPolicy - InitiatorFilterPolicy enum holding the type of scanning filter to apply 31 useExtended - Use the extended advertising commands instead of legacy commands 32 initiatingPHYs - PHYs to initiate connection on; Only used if useExtended is set 33 """ 34 def __init__(self, transport, initiator, peer, trace, initiatorAddress, peerAddress, filterPolicy=InitiatorFilterPolicy.FILTER_NONE, useExtended=False, initiatingPHYs=0x00): 35 self.transport = transport; 36 self.initiator = initiator; 37 self.peer = peer; 38 self.trace = trace; 39 self.initiatorAddress = initiatorAddress; 40 self.peerAddress = peerAddress; 41 self.filterPolicy = filterPolicy; 42 self.useExtended = useExtended; 43 self.initiatingPHYs = initiatingPHYs; 44 45 self.RPAs = [ [ 0 for _ in range(6) ], [ 0 for _ in range(6) ] ]; 46 self.handles = [-1, -1]; 47 self.reasons = [-1, -1]; 48 self.txPhys, self.rxPhys = -1, -1; 49 50 """ 51 Note: intervalMin shall not be greater than intervalMax. 52 supervisionTimeout in milliseconds shall be larger than (1 + latency) * intervalMax * 2, where intervalMax is in milliseconds. 53 54 With intervalMax = 6 -> supervisionTimeout > 12 * 1.25 ms = 15 ms (with a latency of zero) 55 intervalMax = 24 -> supervisionTimeout > 48 * 1.25 ms = 60 ms (with a latency of zero) 56 intervalMax = 3200 -> supervisionTimeout > 6400 * 1.25 ms = 8000 ms (with a latency of zero) 57 """ 58 self.scanInterval = 32; # Scan Interval := 32 x 0.625 ms = 20 ms 59 self.scanWindow = 32; # Scan Window := 32 x 0.625 ms = 20 ms 60 self.intervalMin = 24; # Minimum Connection interval := 24 x 1.25 ms = 30 ms 61 self.intervalMax = 24; # Maximum Connection interval := 24 x 1.25 ms = 30 ms 62 self.latency = 0; # Don't allow peer to skip any connection events 63 self.supervisionTimeout = 25; # Supervision timeout := 25 x 10 ms = 250 ms. 64 self.minCeLen = 0; # Minimum Connection event interval = 0 x 0.625 ms = 0 ms 65 self.maxCeLen = 0; # Maximum Connection event interval = 0 x 0.625 ms = 0 ms 66 67 self.status = 0; 68 self.prevInterval = 0; 69 self.pre_connected, self.__rolesSwitched = False, False; 70 self.__savedEvent = [ None, None ]; 71 72 self.updInitiatorRequest, self.updPeerRequest, self.checkPrematureDisconnect = False, False, True; 73 74 self.cpr_handle, self.cpr_minInterval, self.cpr_maxInterval, self.cpr_latency, self.cpr_timeout = -1, 0, 0, 0, 0; 75 76 """ 77 Private Event handler procedures... 78 79 Check for LE Connection Complete Event | LE Enhanced Connection Complete Event... 80 """ 81 def __hasConnectionCompleteEvent(self, idx, timeout): 82 83 handle, role, interval = -1, -1, -1; 84 localRPA = Address(None, None); 85 86 success = has_event(self.transport, idx, timeout)[0]; 87 if success: 88 event = get_event(self.transport, idx, 200); 89 self.trace.trace(7, str(event)); 90 if event.subEvent == MetaEvents.BT_HCI_EVT_LE_CONN_COMPLETE: 91 self.status, handle, role, address, interval, latency, timeout, accuracy = event.decode(); 92 success = self.status == 0; 93 elif event.subEvent == MetaEvents.BT_HCI_EVT_LE_ENH_CONN_COMPLETE: 94 self.status, handle, role, address, localRPA, peerRPA, interval, latency, timeout, accuracy = event.decode(); 95 success = self.status == 0; 96 else: 97 success = False; 98 return success, handle, role, localRPA.address, interval; 99 100 """ 101 Check for Disconnect Complete Event... 102 """ 103 def __hasDisconnectCompleteEvent(self, idx, timeout): 104 105 handle, reason = -1, -1; 106 107 success, number_of_events = has_event(self.transport, idx, timeout); 108 109 while number_of_events > 0: 110 event = get_event(self.transport, idx, 200); 111 self.trace.trace(7, str(event)); 112 if event.event == Events.BT_HCI_EVT_DISCONN_COMPLETE: 113 self.status, handle, reason = event.decode(); 114 success = self.status == 0; 115 break 116 else: 117 # When waiting for a disconnect event, we usually don't care about 118 # other events. Discard them. 119 self.trace.trace(3, "Warning: Discarding event before disconnect % s" % str(event)); 120 success = False; 121 number_of_events -= 1 122 return success, handle, reason; 123 124 """ 125 Check for LE PHY Update Complete Event... 126 """ 127 def __hasPhysUpdateCompleteEvent(self, idx, timeout): 128 129 txPhys, rxPhys = -1, -1; 130 131 success = has_event(self.transport, idx, timeout)[0]; 132 if success: 133 event = get_event(self.transport, idx, 200); 134 self.trace.trace(7, str(event)); 135 if event.subEvent == MetaEvents.BT_HCI_EVT_LE_PHY_UPDATE_COMPLETE: 136 self.status, handle, txPhys, rxPhys = event.decode(); 137 success = self.status == 0; 138 else: 139 success = False; 140 return success, txPhys, rxPhys; 141 142 """ 143 Check for LE Connection Parameter Update Request Event... 144 """ 145 def __hasConnectionParamRequestEvent(self, idx, timeout): 146 147 handle, minInterval, maxInterval, latency, supervisionTimeout = -1, -1, -1, -1, -1; 148 149 success = has_event(self.transport, idx, timeout)[0]; 150 while success: 151 event = get_event(self.transport, idx, 200); 152 self.trace.trace(7, str(event)); 153 if event.subEvent == MetaEvents.BT_HCI_EVT_LE_CONN_PARAM_REQ: 154 handle, minInterval, maxInterval, latency, supervisionTimeout = event.decode(); 155 break; 156 else: 157 """ 158 We didn't get a LE Connection Parameter Update Request Event... 159 We could receive a LE Connection Parameter Update Complete Event instead - save it! 160 """ 161 self.__savedEvent[idx] = event; 162 success = has_event(self.transport, idx, timeout)[0]; 163 164 return success, handle, minInterval, maxInterval, latency, supervisionTimeout; 165 166 """ 167 Check for LE Connection Parameter Update Complete Event... 168 """ 169 def __hasConnectionUpdateCompleteEvent(self, idx, timeout): 170 status, handle, interval, latency, visionTimeout = -1, -1, -1, -1, -1; 171 172 success = not self.__savedEvent[idx] is None; 173 if success: 174 event = self.__savedEvent[idx]; 175 self.__savedEvent[idx] = None; 176 else: 177 success = has_event(self.transport, idx, timeout)[0]; 178 if success: 179 event = get_event(self.transport, idx, 200); 180 181 if success: 182 self.trace.trace(7, str(event)); 183 if event.subEvent == MetaEvents.BT_HCI_EVT_LE_CONN_UPDATE_COMPLETE: 184 status, handle, interval, latency, visionTimeout = event.decode(); 185 success = status == 0; 186 187 return success, status, handle, interval, latency, visionTimeout; 188 189 """ 190 Send Create Connection command... 191 """ 192 def __initiating(self): 193 194 try: 195 if self.useExtended: 196 # Count number of PHYs (via number of bits set) and create appropriate array arguments 197 PHYCount = bin(self.initiatingPHYs).count("1") 198 if PHYCount < 1 or PHYCount > 3: 199 self.trace.trace(3, "Initiating connection failed: Invalid initiatingPHYs value") 200 return False 201 202 scanInterval = [self.scanInterval for _ in range(PHYCount)] 203 scanWindow = [self.scanWindow for _ in range(PHYCount)] 204 connIntervalMin = [self.intervalMin for _ in range(PHYCount)] 205 connIntervalMax = [self.intervalMax for _ in range(PHYCount)] 206 maxLatency = [self.latency for _ in range(PHYCount)] 207 supervisionTimeout = [self.supervisionTimeout for _ in range(PHYCount)] 208 minCeLen = [self.minCeLen for _ in range(PHYCount)] 209 maxCeLen = [self.maxCeLen for _ in range(PHYCount)] 210 211 le_extended_create_connection(self.transport, self.initiator, self.filterPolicy, self.initiatorAddress.type, self.peerAddress.type, 212 self.peerAddress.address, self.initiatingPHYs, scanInterval, scanWindow, connIntervalMin, connIntervalMax, 213 maxLatency, supervisionTimeout, minCeLen, maxCeLen, 200) 214 else: 215 le_create_connection(self.transport, self.initiator, self.scanInterval, self.scanWindow, self.filterPolicy, self.peerAddress.type, \ 216 self.peerAddress.address, self.initiatorAddress.type, self.intervalMin, self.intervalMax, self.latency, \ 217 self.supervisionTimeout, self.minCeLen, self.maxCeLen, 200); 218 219 event = get_event(self.transport, self.initiator, 200); 220 self.trace.trace(7, str(event)); 221 success = event.isCommandStatus(); 222 if success: 223 self.status = event.decode()[-1]; 224 success = self.status == 0; 225 except Exception as e: 226 self.trace.trace(3, "LE (Extended) Create Connection Command failed: %s" % str(e)); 227 success = False; 228 229 return success; 230 231 """ 232 Send Disconnect command... 233 """ 234 def __terminating(self, reason): 235 236 try: 237 success = False; 238 disconnect(self.transport, self.initiator, self.handles[0], reason, 200); 239 while has_event(self.transport, self.initiator, 200)[0]: 240 event = get_event(self.transport, self.initiator, 200); 241 self.trace.trace(7, str(event)); 242 if event.isCommandStatus(): 243 opcode, status = event.decode()[1:]; 244 if opcode == HCICommands.BT_HCI_OP_DISCONNECT: 245 self.status = status; 246 success = status == 0; 247 break; 248 249 except Exception as e: 250 self.trace.trace(3, "Disconnect Command failed: %s" % str(e)); 251 success = False; 252 253 return success; 254 255 """ 256 Send Create Connection Cancel command... 257 """ 258 def __cancel(self): 259 260 status = le_create_connection_cancel(self.transport, self.initiator, 200); 261 event = get_event(self.transport, self.initiator, 200); 262 self.trace.trace(7, str(event)); 263 return event.isCommandComplete() and (status == 0); 264 265 """ 266 Send LE Connection Update command... 267 """ 268 def __update(self, minInterval, maxInterval, latency, timeout): 269 270 try: 271 if 4 * timeout <= (1+latency) * maxInterval: 272 timeout = (((1+latency) * maxInterval) + 7) / 4; 273 274 self.status = le_connection_update(self.transport, self.initiator, self.handles[0], minInterval, maxInterval, latency, timeout, self.minCeLen, self.maxCeLen, 200); 275 276 event = get_event(self.transport, self.initiator, 200); 277 self.trace.trace(7, str(event)); 278 success = event.isCommandStatus() and (self.status == 0); 279 except Exception as e: 280 self.trace.trace(3, "LE Connection Update Command failed: %s" % str(e)); 281 success = False; 282 283 return success; 284 285 """ 286 Send LE Set PHY command... 287 """ 288 def __updatePhys(self, allPhys, txPhys, rxPhys, optionPhys): 289 290 try: 291 self.status = le_set_phy(self.transport, self.initiator, self.handles[0], allPhys, txPhys, rxPhys, optionPhys, 200); 292 293 event = get_event(self.transport, self.initiator, 200); 294 self.trace.trace(7, str(event)); 295 success = event.isCommandStatus() and (self.status == 0); 296 except Exception as e: 297 self.trace.trace(3, "LE Set PHY Command failed: %s" % str(e)); 298 success = False; 299 300 return success; 301 302 def __updatedPhys(self): 303 """ 304 Initiator should receive a LE_PHY_Update_Complete event to signal that the command has been effectuated (could be no change)... 305 """ 306 success, txPhys, rxPhys = self.__hasPhysUpdateCompleteEvent(self.initiator, 1000); 307 308 if success and ((txPhys != self.txPhys) or (rxPhys != self.rxPhys)): 309 self.txPhys = txPhys; self.rxPhys = rxPhys; 310 """ 311 If Phys has changed - Peer should also receive a LE_PHY_Update_Complete event to signal that the command has been effectuated... 312 """ 313 if not self.peer is None: 314 success, txPhys, rxPhys = self.__hasPhysUpdateCompleteEvent(self.peer, 200); 315 316 return success; 317 318 """ 319 Carry out first part of the connect procedure... 320 Send connect request and wait for command status event 321 """ 322 def preConnect(self): 323 self.pre_connected = self.__initiating(); 324 return self.pre_connected; 325 326 """ 327 Carry out last part of the connect procedure... 328 Wait for (connection complete | enhanced connection complete) events 329 """ 330 def postConnect(self, timeout=600): 331 success = self.pre_connected; 332 """ 333 Both initiator and peer can receive a LE Connection Complete Event if connection succeeds... 334 """ 335 if success: 336 """ 337 Check for LE Connection Complete Event | LE Enhanced Connection Complete Event in initiator... 338 """ 339 initiatorConnected, handle, role, localRPA, interval = self.__hasConnectionCompleteEvent(self.initiator, timeout); 340 if initiatorConnected: 341 self.handles[0] = handle; self.prevInterval = interval; 342 self.RPAs[0] = localRPA[ : ]; 343 self.trace.trace(7, "%s is %s" % ('UpperTester' if self.initiator == 0 else 'LowerTester', 'CENTRAL' if role == 0 else 'PERIPHERAL')); 344 """ 345 Check for LE Connection Complete Event | LE Enhanced Connection Complete Event in peer... 346 """ 347 if not self.peer is None: 348 peerConnected, handle, role, localRPA, interval = self.__hasConnectionCompleteEvent(self.peer, timeout); 349 if peerConnected: 350 self.handles[1] = handle; 351 self.RPAs[1] = localRPA[ : ]; 352 self.trace.trace(7, "%s is %s" % ('UpperTester' if self.peer == 0 else 'LowerTester', 'CENTRAL' if role == 0 else 'PERIPHERAL')); 353 else: 354 peerConnected = True; 355 """ 356 Check for Disconnection Complete Event in initiator... 357 """ 358 if self.checkPrematureDisconnect: 359 initiatorDisconnected, handle, reason = self.__hasDisconnectCompleteEvent(self.initiator, 200); 360 if initiatorDisconnected: 361 self.handles[0] = -1; 362 initiatorConnected = False; 363 364 return success and initiatorConnected and peerConnected; 365 366 """ 367 Carry out the connect procedure... 368 """ 369 def connect(self, timeout=600): 370 success = self.preConnect(); 371 success = success and self.postConnect(timeout); 372 if success: 373 self.txPhys = self.rxPhys = 1; 374 375 return success; 376 377 """ 378 Disconnect... 379 """ 380 def disconnect(self, reason): 381 success = self.__terminating(reason) if not self.handles[0] == -1 else False; 382 """ 383 Both initiator and peer can receive a Disconnection Complete Events... 384 """ 385 if success: 386 initiatorDisconnected, handle, reason = self.__hasDisconnectCompleteEvent(self.initiator, 100 * int((99 + 10 * self.prevInterval * 1.25) / 100)); 387 if initiatorDisconnected: 388 self.handles[0] = -1; self.reasons[0] = reason; 389 390 if not self.peer is None: 391 peerDisconnected, handle, reason = self.__hasDisconnectCompleteEvent(self.peer, 200); 392 if peerDisconnected: 393 self.handles[1] = -1; self.reasons[1] = reason; 394 else: 395 peerDisconnected = True; 396 397 return success and initiatorDisconnected and peerDisconnected; 398 399 def cancelConnect(self): 400 success = self.pre_connected; 401 402 if success: 403 success = self.__cancel(); 404 if success: 405 self.__hasConnectionCompleteEvent(self.initiator, 200)[0]; 406 407 return success; 408 409 """ 410 Update connection parameters... 411 """ 412 def update(self, minInterval, maxInterval, latency, timeout): 413 self.updInitiatorRequest = self.__update(minInterval, maxInterval, latency, timeout) if not self.handles[0] == -1 else False; 414 if self.updInitiatorRequest: 415 if not self.peer is None: 416 # NOTE: Wait upto 3 intervals for Connection Parameter Response to be dispatched on air 417 # FIXME: Using 10 intervals to pass incorrect LL/CON/CEN/BV-27-C implementation, where TST is generating the LL_REJECT_EXT_IND 418 # instead of controller detecting the Different Transaction Collision. 419 # Zephyr controller as tester will not generate Connection Parameter Request if it is in Channel Map procedure 420 # and waiting for instant to pass. 421 self.updPeerRequest, self.cpr_handle, self.cpr_minInterval, self.cpr_maxInterval, self.cpr_latency, self.cpr_timeout = \ 422 self.__hasConnectionParamRequestEvent(self.peer, 100 * int((99 + 10 * self.prevInterval * 1.25) / 100)); 423 else: 424 self.updPeerRequest = False; 425 return self.updInitiatorRequest; 426 427 """ 428 Accept the request for connection parameters update, by responding to the LE Remote Connection Parameter Request Event... 429 """ 430 def acceptUpdate(self): 431 success = self.updInitiatorRequest and self.updPeerRequest; 432 433 if success: 434 """ 435 Send a LL_CONNECTION_PARAM_RSP as a reaction to the LE Remote Connection Parameter Request Event... 436 """ 437 status, handle = le_remote_connection_parameter_request_reply(self.transport, self.peer, self.cpr_handle, self.cpr_minInterval, self.cpr_maxInterval, \ 438 self.cpr_latency, self.cpr_timeout, self.minCeLen, self.maxCeLen, 100); 439 success = status == 0; 440 event = get_event(self.transport, self.peer, 200); 441 self.trace.trace(7, str(event)); 442 success = success and (event.event == Events.BT_HCI_EVT_CMD_COMPLETE); 443 444 return success; 445 446 """ 447 Reject the request for connection parameters update, by responding to the LE Remote Connection Parameter Request Event... 448 """ 449 def rejectUpdate(self, reason): 450 success = self.updInitiatorRequest and self.updPeerRequest; 451 452 if success: 453 """ 454 Send a LL_REJECT_EXT_IND as a reaction to the LE Remote Connection Parameter Request Event... 455 """ 456 status, handle = le_remote_connection_parameter_request_negative_reply(self.transport, self.peer, self.cpr_handle, reason, 200); 457 success = status == 0; 458 event = get_event(self.transport, self.peer, 200); 459 self.trace.trace(7, str(event)); 460 success = success and (event.event == Events.BT_HCI_EVT_CMD_COMPLETE); 461 462 return success; 463 464 def updated(self): 465 success = self.updInitiatorRequest; 466 467 if success: 468 """ 469 Check for LE Connection Update Complete Event in initiator... 470 NOTE: Timing depends on the connect interval in effect - update is usually 12 events after previous reponse event 471 wait upto 3 intervals each for Connection Parameter Response and Connection Update Indication to be dispatched on air 472 then wait another 6 intervals to pass the Connection Update instant 473 Hence wait for 12 intervals before response timeout 474 """ 475 initiatorUpdated, self.status, handle, interval, latency, timeout = \ 476 self.__hasConnectionUpdateCompleteEvent(self.initiator, 100 * int((99 + (12 * self.prevInterval + self.cpr_maxInterval) * 1.25) / 100)); 477 initiatorUpdated = initiatorUpdated and (self.handles[0] == handle); 478 if initiatorUpdated: 479 self.intervalMin = self.intervalMax = self.prevInterval = interval; 480 self.latency = latency; 481 self.supervisionTimeout = timeout; 482 483 """ 484 Check for LE Connection Update Complete Event in peer... 485 """ 486 if not self.peer is None: 487 peerUpdated, status, handle, interval, latency, timeout = self.__hasConnectionUpdateCompleteEvent(self.peer, 100 * int((99 + interval * 1.25) / 100)); 488 peerUpdated = peerUpdated and (self.handles[1] == handle); 489 else: 490 peerUpdated = True; 491 492 success = initiatorUpdated and peerUpdated; 493 494 return success; 495 496 def updatePhys(self, allPhys, txPhys, rxPhys, optionPhys): 497 success = self.__updatePhys(allPhys, txPhys, rxPhys, optionPhys) if not self.handles[0] == -1 else False; 498 499 if success: 500 success = self.__updatedPhys(); 501 502 return success; 503 504 """ 505 Switch roles of initiator and peer - used to let the peer do an update ... 506 """ 507 def switchRoles(self): 508 self.initiator, self.peer = self.peer, self.initiator; 509 self.handles[0], self.handles[1] = self.handles[1], self.handles[0]; 510 self.__rolesSwitched ^= True; 511 512 """ 513 Reset roles of initiator and peer - if they vere switched ... 514 """ 515 def resetRoles(self): 516 if self.__rolesSwitched: 517 self.switchRoles(); 518 519 """ 520 Obtain local Resolvable Private Address from LE Enhanced Connection Complete Event ... 521 """ 522 def localRPA(self): 523 return self.RPAs[0]; 524 525 """ 526 Obtain peer Resolvable Private Address from LE Enhanced Connection Complete Event ... 527 """ 528 def peerRPA(self): 529 return self.RPAs[1]; 530