1#!/usr/bin/env python 2# 3# Copyright (c) 2016, The OpenThread Authors. 4# All rights reserved. 5# 6# Redistribution and use in source and binary forms, with or without 7# modification, are permitted provided that the following conditions are met: 8# 1. Redistributions of source code must retain the above copyright 9# notice, this list of conditions and the following disclaimer. 10# 2. Redistributions in binary form must reproduce the above copyright 11# notice, this list of conditions and the following disclaimer in the 12# documentation and/or other materials provided with the distribution. 13# 3. Neither the name of the copyright holder nor the 14# names of its contributors may be used to endorse or promote products 15# derived from this software without specific prior written permission. 16# 17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 21# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27# POSSIBILITY OF SUCH DAMAGE. 28""" 29>> Thread Host Controller Interface 30>> Device : OpenThread THCI 31>> Class : OpenThread 32""" 33import base64 34import functools 35import ipaddress 36import logging 37import random 38import traceback 39import re 40import socket 41import time 42import json 43from abc import abstractmethod 44 45import serial 46from Queue import Queue 47from serial.serialutil import SerialException 48 49from GRLLibs.ThreadPacket.PlatformPackets import ( 50 PlatformDiagnosticPacket, 51 PlatformPackets, 52) 53from GRLLibs.UtilityModules.ModuleHelper import ModuleHelper, ThreadRunner 54from GRLLibs.UtilityModules.Plugins.AES_CMAC import Thread_PBKDF2 55from GRLLibs.UtilityModules.Test import ( 56 Thread_Device_Role, 57 Device_Data_Requirement, 58 MacType, 59) 60from GRLLibs.UtilityModules.enums import ( 61 PlatformDiagnosticPacket_Direction, 62 PlatformDiagnosticPacket_Type, 63) 64from GRLLibs.UtilityModules.enums import DevCapb 65 66from IThci import IThci 67import commissioner 68from commissioner_impl import OTCommissioner 69 70# Replace by the actual version string for the vendor's reference device 71OT11_VERSION = 'OPENTHREAD' 72OT12_VERSION = 'OPENTHREAD' 73OT13_VERSION = 'OPENTHREAD' 74 75# Supported device capabilites in this THCI implementation 76OT11_CAPBS = DevCapb.V1_1 77OT12_CAPBS = (DevCapb.L_AIO | DevCapb.C_FFD | DevCapb.C_RFD) 78OT12BR_CAPBS = (DevCapb.C_BBR | DevCapb.C_Host | DevCapb.C_Comm) 79OT13_CAPBS = (DevCapb.C_FTD13 | DevCapb.C_MTD13) 80OT13BR_CAPBS = (DevCapb.C_BR13 | DevCapb.C_Host13) 81 82ZEPHYR_PREFIX = 'ot ' 83"""CLI prefix used for OpenThread commands in Zephyr systems""" 84 85LINESEPX = re.compile(r'\r\n|\n') 86"""regex: used to split lines""" 87 88LOGX = re.compile(r'((\[(-|C|W|N|I|D)\])' 89 r'|(-+$)' # e.x. ------------------------------------------------------------------------ 90 r'|(=+\[.*\]=+$)' # e.x. ==============================[TX len=108]=============================== 91 r'|(\|.+\|.+\|.+)' # e.x. | 61 DC D2 CE FA 04 00 00 | 00 00 0A 6E 16 01 00 00 | aRNz......n.... 92 r')') 93"""regex used to filter logs""" 94 95assert LOGX.match('[-]') 96assert LOGX.match('[C]') 97assert LOGX.match('[W]') 98assert LOGX.match('[N]') 99assert LOGX.match('[I]') 100assert LOGX.match('[D]') 101assert LOGX.match('------------------------------------------------------------------------') 102assert LOGX.match('==============================[TX len=108]===============================') 103assert LOGX.match('| 61 DC D2 CE FA 04 00 00 | 00 00 0A 6E 16 01 00 00 | aRNz......n....') 104 105# OT Errors 106OT_ERROR_ALREADY = 24 107 108logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") 109 110_callStackDepth = 0 111 112 113def watched(func): 114 func_name = func.func_name 115 116 @functools.wraps(func) 117 def wrapped_api_func(self, *args, **kwargs): 118 global _callStackDepth 119 callstr = '====' * _callStackDepth + "> %s%s%s" % (func_name, str(args) if args else "", 120 str(kwargs) if kwargs else "") 121 122 _callStackDepth += 1 123 try: 124 ret = func(self, *args, **kwargs) 125 self.log("%s returns %r", callstr, ret) 126 return ret 127 except Exception as ex: 128 self.log("FUNC %s failed: %s", func_name, str(ex)) 129 raise 130 finally: 131 _callStackDepth -= 1 132 if _callStackDepth == 0: 133 print('\n') 134 135 return wrapped_api_func 136 137 138def retry(n, interval=0): 139 assert n >= 0, n 140 141 def deco(func): 142 143 @functools.wraps(func) 144 def retried_func(*args, **kwargs): 145 for i in range(n + 1): 146 try: 147 return func(*args, **kwargs) 148 except Exception: 149 if i == n: 150 raise 151 152 time.sleep(interval) 153 154 return retried_func 155 156 return deco 157 158 159def API(api_func): 160 try: 161 return watched(api_func) 162 except Exception: 163 tb = traceback.format_exc() 164 ModuleHelper.WriteIntoDebugLogger("Exception raised while calling %s:\n%s" % (api_func.func_name, tb)) 165 raise 166 167 168def commissioning(func): 169 170 @functools.wraps(func) 171 def comm_func(self, *args, **kwargs): 172 self._onCommissionStart() 173 try: 174 return func(self, *args, **kwargs) 175 finally: 176 self._onCommissionStop() 177 178 return comm_func 179 180 181class CommandError(Exception): 182 183 def __init__(self, code, msg): 184 assert isinstance(code, int), code 185 self.code = code 186 self.msg = msg 187 188 super(CommandError, self).__init__("Error %d: %s" % (code, msg)) 189 190 191class OpenThreadTHCI(object): 192 LOWEST_POSSIBLE_PARTATION_ID = 0x1 193 LINK_QUALITY_CHANGE_TIME = 100 194 DEFAULT_COMMAND_TIMEOUT = 10 195 firmwarePrefix = 'OPENTHREAD/' 196 DOMAIN_NAME = 'Thread' 197 MLR_TIMEOUT_MIN = 300 198 NETWORK_ATTACHMENT_TIMEOUT = 10 199 200 IsBorderRouter = False 201 IsHost = False 202 203 externalCommissioner = None 204 _update_router_status = False 205 206 _cmdPrefix = '' 207 _lineSepX = LINESEPX 208 209 _ROLE_MODE_DICT = { 210 Thread_Device_Role.Leader: 'rdn', 211 Thread_Device_Role.Router: 'rdn', 212 Thread_Device_Role.SED: '-', 213 Thread_Device_Role.EndDevice: 'rn', 214 Thread_Device_Role.REED: 'rdn', 215 Thread_Device_Role.EndDevice_FED: 'rdn', 216 Thread_Device_Role.EndDevice_MED: 'rn', 217 Thread_Device_Role.SSED: '-', 218 } 219 220 def __init__(self, **kwargs): 221 """initialize the serial port and default network parameters 222 Args: 223 **kwargs: Arbitrary keyword arguments 224 Includes 'EUI' and 'SerialPort' 225 """ 226 self.intialize(kwargs) 227 228 @abstractmethod 229 def _connect(self): 230 """ 231 Connect to the device. 232 """ 233 234 @abstractmethod 235 def _disconnect(self): 236 """ 237 Disconnect from the device 238 """ 239 240 @abstractmethod 241 def _cliReadLine(self): 242 """Read exactly one line from the device 243 244 Returns: 245 None if no data 246 """ 247 248 @abstractmethod 249 def _cliWriteLine(self, line): 250 """Send exactly one line to the device 251 252 Args: 253 line str: data send to device 254 """ 255 256 @abstractmethod 257 def _onCommissionStart(self): 258 """Called when commissioning starts.""" 259 260 @abstractmethod 261 def _onCommissionStop(self): 262 """Called when commissioning stops.""" 263 264 def __sendCommand(self, cmd, expectEcho=True): 265 cmd = self._cmdPrefix + cmd 266 # self.log("command: %s", cmd) 267 self._cliWriteLine(cmd) 268 if expectEcho: 269 self.__expect(cmd, endswith=True) 270 271 _COMMAND_OUTPUT_ERROR_PATTERN = re.compile(r'Error (\d+): (.*)') 272 273 @retry(3) 274 @watched 275 def __executeCommand(self, cmd, timeout=DEFAULT_COMMAND_TIMEOUT): 276 """send specific command to reference unit over serial port 277 278 Args: 279 cmd: OpenThread CLI string 280 281 Returns: 282 Done: successfully send the command to reference unit and parse it 283 Value: successfully retrieve the desired value from reference unit 284 Error: some errors occur, indicates by the followed specific error number 285 """ 286 if self.logThreadStatus == self.logStatus['running']: 287 self.logThreadStatus = self.logStatus['pauseReq'] 288 while (self.logThreadStatus != self.logStatus['paused'] and 289 self.logThreadStatus != self.logStatus['stop']): 290 pass 291 292 try: 293 self.__sendCommand(cmd) 294 response = [] 295 296 t_end = time.time() + timeout 297 while time.time() < t_end: 298 line = self.__readCliLine() 299 if line is None: 300 time.sleep(0.01) 301 continue 302 303 # self.log("readline: %s", line) 304 # skip empty lines 305 if line: 306 response.append(line) 307 308 if line.endswith('Done'): 309 break 310 else: 311 m = OpenThreadTHCI._COMMAND_OUTPUT_ERROR_PATTERN.match(line) 312 if m is not None: 313 code, msg = m.groups() 314 raise CommandError(int(code), msg) 315 else: 316 raise Exception('%s: failed to find end of response: %s' % (self, response)) 317 318 except SerialException as e: 319 self.log('__executeCommand() Error: ' + str(e)) 320 self._disconnect() 321 self._connect() 322 raise e 323 324 return response 325 326 def __expect(self, expected, timeout=5, endswith=False): 327 """Find the `expected` line within `times` tries. 328 329 Args: 330 expected str: the expected string 331 times int: number of tries 332 """ 333 #self.log('Expecting [%s]' % (expected)) 334 335 deadline = time.time() + timeout 336 while True: 337 line = self.__readCliLine() 338 if line is not None: 339 #self.log("readline: %s", line) 340 pass 341 342 if line is None: 343 if time.time() >= deadline: 344 break 345 346 self.sleep(0.01) 347 continue 348 349 matched = line.endswith(expected) if endswith else line == expected 350 if matched: 351 #self.log('Expected [%s]' % (expected)) 352 return 353 354 raise Exception('failed to find expected string[%s]' % expected) 355 356 def __readCliLine(self, ignoreLogs=True): 357 """Read the next line from OT CLI.d""" 358 line = self._cliReadLine() 359 if ignoreLogs: 360 while line is not None and LOGX.match(line): 361 line = self._cliReadLine() 362 363 return line 364 365 @API 366 def getVersionNumber(self): 367 """get OpenThread stack firmware version number""" 368 return self.__executeCommand('version')[0] 369 370 def log(self, fmt, *args): 371 try: 372 msg = fmt % args 373 # print('%s - %s - %s' % (self.port, time.strftime('%b %d %H:%M:%S'), msg)) 374 print('%s %s' % (self.port, msg)) 375 except Exception: 376 pass 377 378 def sleep(self, duration): 379 if duration >= 1: 380 self.log("sleeping for %ss ...", duration) 381 time.sleep(duration) 382 383 @API 384 def intialize(self, params): 385 """initialize the serial port with baudrate, timeout parameters""" 386 self.port = params.get('SerialPort', '') 387 # params example: {'EUI': 1616240311388864514L, 'SerialBaudRate': None, 'TelnetIP': '192.168.8.181', 'SerialPort': None, 'Param7': None, 'Param6': None, 'Param5': 'ip', 'TelnetPort': '22', 'Param9': None, 'Param8': None} 388 389 try: 390 391 ipaddress.ip_address(self.port) 392 # handle TestHarness Discovery Protocol 393 self.connectType = 'ip' 394 self.telnetIp = self.port 395 self.telnetPort = 22 396 self.telnetUsername = 'pi' if params.get('Param6') is None else params.get('Param6') 397 self.telnetPassword = 'raspberry' if params.get('Param7') is None else params.get('Param7') 398 except ValueError: 399 self.connectType = (params.get('Param5') or 'usb').lower() 400 self.telnetIp = params.get('TelnetIP') 401 self.telnetPort = int(params.get('TelnetPort')) if params.get('TelnetPort') else 22 402 # username for SSH 403 self.telnetUsername = 'pi' if params.get('Param6') is None else params.get('Param6') 404 # password for SSH 405 self.telnetPassword = 'raspberry' if params.get('Param7') is None else params.get('Param7') 406 407 self.mac = params.get('EUI') 408 self.backboneNetif = params.get('Param8') or 'eth0' 409 self.extraParams = self.__parseExtraParams(params.get('Param9')) 410 411 self.UIStatusMsg = '' 412 self.AutoDUTEnable = False 413 self.isPowerDown = False 414 self._is_net = False # whether device is through ser2net 415 self.logStatus = { 416 'stop': 'stop', 417 'running': 'running', 418 'pauseReq': 'pauseReq', 419 'paused': 'paused', 420 } 421 self.joinStatus = { 422 'notstart': 'notstart', 423 'ongoing': 'ongoing', 424 'succeed': 'succeed', 425 'failed': 'failed', 426 } 427 self.logThreadStatus = self.logStatus['stop'] 428 429 self.deviceConnected = False 430 431 # init serial port 432 self._connect() 433 if not self.IsBorderRouter: 434 self.__detectZephyr() 435 self.__discoverDeviceCapability() 436 self.UIStatusMsg = self.getVersionNumber() 437 438 if self.firmwarePrefix in self.UIStatusMsg: 439 self.deviceConnected = True 440 else: 441 self.UIStatusMsg = ('Firmware Not Matching Expecting ' + self.firmwarePrefix + ', Now is ' + 442 self.UIStatusMsg) 443 ModuleHelper.WriteIntoDebugLogger('Err: OpenThread device Firmware not matching..') 444 445 def __repr__(self): 446 if self.connectType == 'ip': 447 return '[%s:%d]' % (self.telnetIp, self.telnetPort) 448 else: 449 return '[%s]' % self.port 450 451 @watched 452 def __parseExtraParams(self, Param9): 453 """ 454 Parse `Param9` for extra THCI parameters. 455 456 `Param9` should be a JSON string encoded in URL-safe base64 encoding. 457 458 Defined Extra THCI Parameters: 459 - "cmd-start-otbr-agent" : The command to start otbr-agent (default: systemctl start otbr-agent) 460 - "cmd-stop-otbr-agent" : The command to stop otbr-agent (default: systemctl stop otbr-agent) 461 - "cmd-restart-otbr-agent" : The command to restart otbr-agent (default: systemctl restart otbr-agent) 462 463 For example, Param9 can be generated as below: 464 Param9 = base64.urlsafe_b64encode(json.dumps({ 465 "cmd-start-otbr-agent": "service otbr-agent start", 466 "cmd-stop-otbr-agent": "service otbr-agent stop", 467 "cmd-restart-otbr-agent": "service otbr-agent restart", 468 })) 469 470 :param Param9: A JSON string encoded in URL-safe base64 encoding. 471 :return: A dict containing extra THCI parameters. 472 """ 473 if not Param9 or not Param9.strip(): 474 return {} 475 476 jsonStr = base64.urlsafe_b64decode(Param9) 477 params = json.loads(jsonStr) 478 return params 479 480 @API 481 def closeConnection(self): 482 """close current serial port connection""" 483 self._disconnect() 484 485 def __disableRouterEligible(self): 486 """disable router role 487 """ 488 cmd = 'routereligible disable' 489 self.__executeCommand(cmd) 490 491 def __setDeviceMode(self, mode): 492 """set thread device mode: 493 494 Args: 495 mode: thread device mode 496 r: rx-on-when-idle 497 s: secure IEEE 802.15.4 data request 498 d: full thread device 499 n: full network data 500 501 Returns: 502 True: successful to set the device mode 503 False: fail to set the device mode 504 """ 505 cmd = 'mode %s' % mode 506 return self.__executeCommand(cmd)[-1] == 'Done' 507 508 def __setRouterUpgradeThreshold(self, iThreshold): 509 """set router upgrade threshold 510 511 Args: 512 iThreshold: the number of active routers on the Thread network 513 partition below which a REED may decide to become a Router. 514 515 Returns: 516 True: successful to set the ROUTER_UPGRADE_THRESHOLD 517 False: fail to set ROUTER_UPGRADE_THRESHOLD 518 """ 519 cmd = 'routerupgradethreshold %s' % str(iThreshold) 520 return self.__executeCommand(cmd)[-1] == 'Done' 521 522 def __setRouterDowngradeThreshold(self, iThreshold): 523 """set router downgrade threshold 524 525 Args: 526 iThreshold: the number of active routers on the Thread network 527 partition above which an active router may decide to 528 become a child. 529 530 Returns: 531 True: successful to set the ROUTER_DOWNGRADE_THRESHOLD 532 False: fail to set ROUTER_DOWNGRADE_THRESHOLD 533 """ 534 cmd = 'routerdowngradethreshold %s' % str(iThreshold) 535 return self.__executeCommand(cmd)[-1] == 'Done' 536 537 def __setRouterSelectionJitter(self, iRouterJitter): 538 """set ROUTER_SELECTION_JITTER parameter for REED to upgrade to Router 539 540 Args: 541 iRouterJitter: a random period prior to request Router ID for REED 542 543 Returns: 544 True: successful to set the ROUTER_SELECTION_JITTER 545 False: fail to set ROUTER_SELECTION_JITTER 546 """ 547 cmd = 'routerselectionjitter %s' % str(iRouterJitter) 548 return self.__executeCommand(cmd)[-1] == 'Done' 549 550 def __setAddressfilterMode(self, mode): 551 """set address filter mode 552 553 Returns: 554 True: successful to set address filter mode. 555 False: fail to set address filter mode. 556 """ 557 cmd = 'macfilter addr ' + mode 558 return self.__executeCommand(cmd)[-1] == 'Done' 559 560 def __startOpenThread(self): 561 """start OpenThread stack 562 563 Returns: 564 True: successful to start OpenThread stack and thread interface up 565 False: fail to start OpenThread stack 566 """ 567 if self.hasActiveDatasetToCommit: 568 if self.__executeCommand('dataset commit active')[0] != 'Done': 569 raise Exception('failed to commit active dataset') 570 else: 571 self.hasActiveDatasetToCommit = False 572 573 # Restore allowlist/denylist address filter mode if rejoin after 574 # reset 575 if self.isPowerDown: 576 if self._addressfilterMode == 'allowlist': 577 if self.__setAddressfilterMode('allowlist'): 578 for addr in self._addressfilterSet: 579 self.addAllowMAC(addr) 580 elif self._addressfilterMode == 'denylist': 581 if self.__setAddressfilterMode('denylist'): 582 for addr in self._addressfilterSet: 583 self.addBlockedMAC(addr) 584 585 # Set routerselectionjitter to 1 for certain device roles 586 if self.deviceRole in [ 587 Thread_Device_Role.Leader, 588 Thread_Device_Role.Router, 589 Thread_Device_Role.REED, 590 ]: 591 self.__setRouterSelectionJitter(1) 592 elif self.deviceRole in [Thread_Device_Role.BR_1, Thread_Device_Role.BR_2]: 593 self.__setRouterSelectionJitter(1) 594 595 if self.DeviceCapability == OT12BR_CAPBS: 596 # Configure default BBR dataset 597 self.__configBbrDataset(SeqNum=self.bbrSeqNum, 598 MlrTimeout=self.bbrMlrTimeout, 599 ReRegDelay=self.bbrReRegDelay) 600 # Add default domain prefix is not configured otherwise 601 if self.__useDefaultDomainPrefix: 602 self.__addDefaultDomainPrefix() 603 604 self.__executeCommand('ifconfig up') 605 self.__executeCommand('thread start') 606 self.isPowerDown = False 607 return True 608 609 @watched 610 def __isOpenThreadRunning(self): 611 """check whether or not OpenThread is running 612 613 Returns: 614 True: OpenThread is running 615 False: OpenThread is not running 616 """ 617 return self.__executeCommand('state')[0] != 'disabled' 618 619 @watched 620 def __isDeviceAttached(self): 621 """check whether or not OpenThread is running 622 623 Returns: 624 True: OpenThread is running 625 False: OpenThread is not running 626 """ 627 detached_states = ["detached", "disabled"] 628 return self.__executeCommand('state')[0] not in detached_states 629 630 # rloc16 might be hex string or integer, need to return actual allocated router id 631 def __convertRlocToRouterId(self, xRloc16): 632 """mapping Rloc16 to router id 633 634 Args: 635 xRloc16: hex rloc16 short address 636 637 Returns: 638 actual router id allocated by leader 639 """ 640 routerList = self.__executeCommand('router list')[0].split() 641 rloc16 = None 642 routerid = None 643 644 for index in routerList: 645 cmd = 'router %s' % index 646 router = self.__executeCommand(cmd) 647 648 for line in router: 649 if 'Done' in line: 650 break 651 elif 'Router ID' in line: 652 routerid = line.split()[2] 653 elif 'Rloc' in line: 654 rloc16 = line.split()[1] 655 else: 656 pass 657 658 # process input rloc16 659 if isinstance(xRloc16, str): 660 rloc16 = '0x' + rloc16 661 if rloc16 == xRloc16: 662 return routerid 663 elif isinstance(xRloc16, int): 664 if int(rloc16, 16) == xRloc16: 665 return routerid 666 else: 667 pass 668 669 return None 670 671 # pylint: disable=no-self-use 672 def __convertLongToHex(self, iValue, fillZeros=None): 673 """convert a long hex integer to string 674 remove '0x' and 'L' return string 675 676 Args: 677 iValue: long integer in hex format 678 fillZeros: pad string with zeros on the left to specified width 679 680 Returns: 681 string of this long integer without '0x' and 'L' 682 """ 683 fmt = '%x' 684 if fillZeros is not None: 685 fmt = '%%0%dx' % fillZeros 686 687 return fmt % iValue 688 689 @commissioning 690 def __readCommissioningLogs(self, durationInSeconds): 691 """read logs during the commissioning process 692 693 Args: 694 durationInSeconds: time duration for reading commissioning logs 695 696 Returns: 697 Commissioning logs 698 """ 699 self.logThreadStatus = self.logStatus['running'] 700 logs = Queue() 701 t_end = time.time() + durationInSeconds 702 joinSucceed = False 703 704 while time.time() < t_end: 705 706 if self.logThreadStatus == self.logStatus['pauseReq']: 707 self.logThreadStatus = self.logStatus['paused'] 708 709 if self.logThreadStatus != self.logStatus['running']: 710 self.sleep(0.01) 711 continue 712 713 try: 714 line = self.__readCliLine(ignoreLogs=False) 715 716 if line: 717 self.log("commissioning log: %s", line) 718 logs.put(line) 719 720 if 'Join success' in line: 721 joinSucceed = True 722 # read commissioning logs for 3 more seconds 723 t_end = time.time() + 3 724 elif 'Join failed' in line: 725 # read commissioning logs for 3 more seconds 726 t_end = time.time() + 3 727 elif line is None: 728 self.sleep(0.01) 729 730 except Exception: 731 pass 732 733 self.joinCommissionedStatus = self.joinStatus['succeed'] if joinSucceed else self.joinStatus['failed'] 734 self.logThreadStatus = self.logStatus['stop'] 735 return logs 736 737 # pylint: disable=no-self-use 738 def __convertChannelMask(self, channelsArray): 739 """convert channelsArray to bitmask format 740 741 Args: 742 channelsArray: channel array (i.e. [21, 22]) 743 744 Returns: 745 bitmask format corresponding to a given channel array 746 """ 747 maskSet = 0 748 749 for eachChannel in channelsArray: 750 mask = 1 << eachChannel 751 maskSet = maskSet | mask 752 753 return maskSet 754 755 def __setChannelMask(self, channelMask): 756 cmd = 'dataset channelmask %s' % channelMask 757 self.hasActiveDatasetToCommit = True 758 return self.__executeCommand(cmd)[-1] == 'Done' 759 760 def __setSecurityPolicy(self, securityPolicySecs, securityPolicyFlags): 761 cmd = 'dataset securitypolicy %s %s' % ( 762 str(securityPolicySecs), 763 securityPolicyFlags, 764 ) 765 self.hasActiveDatasetToCommit = True 766 return self.__executeCommand(cmd)[-1] == 'Done' 767 768 def __setKeySwitchGuardTime(self, iKeySwitchGuardTime): 769 """ set the Key switch guard time 770 771 Args: 772 iKeySwitchGuardTime: key switch guard time 773 774 Returns: 775 True: successful to set key switch guard time 776 False: fail to set key switch guard time 777 """ 778 cmd = 'keysequence guardtime %s' % str(iKeySwitchGuardTime) 779 if self.__executeCommand(cmd)[-1] == 'Done': 780 self.sleep(1) 781 return True 782 else: 783 return False 784 785 def __getCommissionerSessionId(self): 786 """ get the commissioner session id allocated from Leader """ 787 return self.__executeCommand('commissioner sessionid')[0] 788 789 # pylint: disable=no-self-use 790 def _deviceEscapeEscapable(self, string): 791 """Escape CLI escapable characters in the given string. 792 793 Args: 794 string (str): UTF-8 input string. 795 796 Returns: 797 [str]: The modified string with escaped characters. 798 """ 799 escapable_chars = '\\ \t\r\n' 800 for char in escapable_chars: 801 string = string.replace(char, '\\%s' % char) 802 return string 803 804 @API 805 def setNetworkName(self, networkName='GRL'): 806 """set Thread Network name 807 808 Args: 809 networkName: the networkname string to be set 810 811 Returns: 812 True: successful to set the Thread Networkname 813 False: fail to set the Thread Networkname 814 """ 815 networkName = self._deviceEscapeEscapable(networkName) 816 cmd = 'networkname %s' % networkName 817 datasetCmd = 'dataset networkname %s' % networkName 818 self.hasActiveDatasetToCommit = True 819 return self.__executeCommand(cmd)[-1] == 'Done' and self.__executeCommand(datasetCmd)[-1] == 'Done' 820 821 @API 822 def setChannel(self, channel=11): 823 """set channel of Thread device operates on. 824 825 Args: 826 channel: 827 (0 - 10: Reserved) 828 (11 - 26: 2.4GHz channels) 829 (27 - 65535: Reserved) 830 831 Returns: 832 True: successful to set the channel 833 False: fail to set the channel 834 """ 835 cmd = 'channel %s' % channel 836 datasetCmd = 'dataset channel %s' % channel 837 self.hasSetChannel = True 838 self.hasActiveDatasetToCommit = True 839 return self.__executeCommand(cmd)[-1] == 'Done' and self.__executeCommand(datasetCmd)[-1] == 'Done' 840 841 @API 842 def getChannel(self): 843 """get current channel""" 844 return self.__executeCommand('channel')[0] 845 846 @API 847 def setMAC(self, xEUI): 848 """set the extended addresss of Thread device 849 850 Args: 851 xEUI: extended address in hex format 852 853 Returns: 854 True: successful to set the extended address 855 False: fail to set the extended address 856 """ 857 if not isinstance(xEUI, str): 858 address64 = self.__convertLongToHex(xEUI, 16) 859 else: 860 address64 = xEUI 861 862 cmd = 'extaddr %s' % address64 863 if self.__executeCommand(cmd)[-1] == 'Done': 864 self.mac = address64 865 return True 866 else: 867 return False 868 869 @API 870 def getMAC(self, bType=MacType.RandomMac): 871 """get one specific type of MAC address 872 currently OpenThread only supports Random MAC address 873 874 Args: 875 bType: indicate which kind of MAC address is required 876 877 Returns: 878 specific type of MAC address 879 """ 880 # if power down happens, return extended address assigned previously 881 if self.isPowerDown: 882 macAddr64 = self.mac 883 else: 884 if bType == MacType.FactoryMac: 885 macAddr64 = self.__executeCommand('eui64')[0] 886 elif bType == MacType.HashMac: 887 macAddr64 = self.__executeCommand('joiner id')[0] 888 elif bType == MacType.EthMac and self.IsBorderRouter: 889 return self._deviceGetEtherMac() 890 else: 891 macAddr64 = self.__executeCommand('extaddr')[0] 892 893 return int(macAddr64, 16) 894 895 @API 896 def getLL64(self): 897 """get link local unicast IPv6 address""" 898 return self.__executeCommand('ipaddr linklocal')[0] 899 900 @API 901 def getRloc16(self): 902 """get rloc16 short address""" 903 rloc16 = self.__executeCommand('rloc16')[0] 904 return int(rloc16, 16) 905 906 @API 907 def getRloc(self): 908 """get router locator unicast Ipv6 address""" 909 return self.__executeCommand('ipaddr rloc')[0] 910 911 def __getGlobal(self): 912 """get global unicast IPv6 address set 913 if configuring multiple entries 914 """ 915 globalAddrs = [] 916 rlocAddr = self.getRloc() 917 918 addrs = self.__executeCommand('ipaddr') 919 920 # take rloc address as a reference for current mesh local prefix, 921 # because for some TCs, mesh local prefix may be updated through 922 # pending dataset management. 923 for ip6Addr in addrs: 924 if ip6Addr == 'Done': 925 break 926 927 fullIp = ModuleHelper.GetFullIpv6Address(ip6Addr).lower() 928 929 if fullIp.startswith('fe80') or fullIp.startswith(rlocAddr[0:19]): 930 continue 931 932 globalAddrs.append(fullIp) 933 934 return globalAddrs 935 936 @API 937 def setNetworkKey(self, key): 938 """set Thread network key 939 940 Args: 941 key: Thread network key used in secure the MLE/802.15.4 packet 942 943 Returns: 944 True: successful to set the Thread network key 945 False: fail to set the Thread network key 946 """ 947 if not isinstance(key, str): 948 networkKey = self.__convertLongToHex(key, 32) 949 cmd = 'networkkey %s' % networkKey 950 datasetCmd = 'dataset networkkey %s' % networkKey 951 else: 952 networkKey = key 953 cmd = 'networkkey %s' % networkKey 954 datasetCmd = 'dataset networkkey %s' % networkKey 955 956 self.networkKey = networkKey 957 self.hasActiveDatasetToCommit = True 958 return self.__executeCommand(cmd)[-1] == 'Done' and self.__executeCommand(datasetCmd)[-1] == 'Done' 959 960 @API 961 def addBlockedMAC(self, xEUI): 962 """add a given extended address to the denylist entry 963 964 Args: 965 xEUI: extended address in hex format 966 967 Returns: 968 True: successful to add a given extended address to the denylist entry 969 False: fail to add a given extended address to the denylist entry 970 """ 971 if isinstance(xEUI, str): 972 macAddr = xEUI 973 else: 974 macAddr = self.__convertLongToHex(xEUI) 975 976 # if blocked device is itself 977 if macAddr == self.mac: 978 print('block device itself') 979 return True 980 981 if self._addressfilterMode != 'denylist': 982 if self.__setAddressfilterMode('denylist'): 983 self._addressfilterMode = 'denylist' 984 985 cmd = 'macfilter addr add %s' % macAddr 986 ret = self.__executeCommand(cmd)[-1] == 'Done' 987 988 self._addressfilterSet.add(macAddr) 989 print('current denylist entries:') 990 for addr in self._addressfilterSet: 991 print(addr) 992 993 return ret 994 995 @API 996 def addAllowMAC(self, xEUI): 997 """add a given extended address to the allowlist addressfilter 998 999 Args: 1000 xEUI: a given extended address in hex format 1001 1002 Returns: 1003 True: successful to add a given extended address to the allowlist entry 1004 False: fail to add a given extended address to the allowlist entry 1005 """ 1006 if isinstance(xEUI, str): 1007 macAddr = xEUI 1008 else: 1009 macAddr = self.__convertLongToHex(xEUI) 1010 1011 if self._addressfilterMode != 'allowlist': 1012 if self.__setAddressfilterMode('allowlist'): 1013 self._addressfilterMode = 'allowlist' 1014 1015 cmd = 'macfilter addr add %s' % macAddr 1016 ret = self.__executeCommand(cmd)[-1] == 'Done' 1017 1018 self._addressfilterSet.add(macAddr) 1019 print('current allowlist entries:') 1020 for addr in self._addressfilterSet: 1021 print(addr) 1022 return ret 1023 1024 @API 1025 def clearBlockList(self): 1026 """clear all entries in denylist table 1027 1028 Returns: 1029 True: successful to clear the denylist 1030 False: fail to clear the denylist 1031 """ 1032 # remove all entries in denylist 1033 print('clearing denylist entries:') 1034 for addr in self._addressfilterSet: 1035 print(addr) 1036 1037 # disable denylist 1038 if self.__setAddressfilterMode('disable'): 1039 self._addressfilterMode = 'disable' 1040 # clear ops 1041 cmd = 'macfilter addr clear' 1042 if self.__executeCommand(cmd)[-1] == 'Done': 1043 self._addressfilterSet.clear() 1044 return True 1045 return False 1046 1047 @API 1048 def clearAllowList(self): 1049 """clear all entries in allowlist table 1050 1051 Returns: 1052 True: successful to clear the allowlist 1053 False: fail to clear the allowlist 1054 """ 1055 # remove all entries in allowlist 1056 print('clearing allowlist entries:') 1057 for addr in self._addressfilterSet: 1058 print(addr) 1059 1060 # disable allowlist 1061 if self.__setAddressfilterMode('disable'): 1062 self._addressfilterMode = 'disable' 1063 # clear ops 1064 cmd = 'macfilter addr clear' 1065 if self.__executeCommand(cmd)[-1] == 'Done': 1066 self._addressfilterSet.clear() 1067 return True 1068 return False 1069 1070 @API 1071 def getDeviceRole(self): 1072 """get current device role in Thread Network""" 1073 return self.__executeCommand('state')[0] 1074 1075 @API 1076 def joinNetwork(self, eRoleId): 1077 """make device ready to join the Thread Network with a given role 1078 1079 Args: 1080 eRoleId: a given device role id 1081 1082 Returns: 1083 True: ready to set Thread Network parameter for joining desired Network 1084 """ 1085 self.deviceRole = eRoleId 1086 mode = '-' 1087 if ModuleHelper.LeaderDutChannelFound and not self.hasSetChannel: 1088 self.channel = ModuleHelper.Default_Channel 1089 1090 # FIXME: when Harness call setNetworkDataRequirement()? 1091 # only sleep end device requires stable networkdata now 1092 if eRoleId == Thread_Device_Role.Leader: 1093 print('join as leader') 1094 mode = 'rdn' 1095 if self.AutoDUTEnable is False: 1096 # set ROUTER_DOWNGRADE_THRESHOLD 1097 self.__setRouterDowngradeThreshold(33) 1098 elif eRoleId == Thread_Device_Role.Router: 1099 print('join as router') 1100 mode = 'rdn' 1101 if self.AutoDUTEnable is False: 1102 # set ROUTER_DOWNGRADE_THRESHOLD 1103 self.__setRouterDowngradeThreshold(33) 1104 elif eRoleId in (Thread_Device_Role.BR_1, Thread_Device_Role.BR_2): 1105 if self.DeviceCapability == OT12BR_CAPBS: 1106 print('join as BBR') 1107 else: 1108 print('join as BR') 1109 mode = 'rdn' 1110 if self.AutoDUTEnable is False: 1111 # set ROUTER_DOWNGRADE_THRESHOLD 1112 self.__setRouterDowngradeThreshold(33) 1113 elif eRoleId == Thread_Device_Role.SED: 1114 print('join as sleepy end device') 1115 mode = '-' 1116 self.__setPollPeriod(self.__sedPollPeriod) 1117 elif eRoleId == Thread_Device_Role.SSED: 1118 print('join as SSED') 1119 mode = '-' 1120 self.setCSLperiod(self.cslPeriod) 1121 self.setCSLtout(self.ssedTimeout) 1122 self.setCSLsuspension(False) 1123 elif eRoleId == Thread_Device_Role.EndDevice: 1124 print('join as end device') 1125 mode = 'rn' 1126 elif eRoleId == Thread_Device_Role.REED: 1127 print('join as REED') 1128 mode = 'rdn' 1129 if self.AutoDUTEnable is False: 1130 # set ROUTER_UPGRADE_THRESHOLD 1131 self.__setRouterUpgradeThreshold(0) 1132 elif eRoleId == Thread_Device_Role.EndDevice_FED: 1133 print('join as FED') 1134 mode = 'rdn' 1135 # always remain an ED, never request to be a router 1136 self.__disableRouterEligible() 1137 elif eRoleId == Thread_Device_Role.EndDevice_MED: 1138 print('join as MED') 1139 mode = 'rn' 1140 else: 1141 pass 1142 1143 # set Thread device mode with a given role 1144 self.__setDeviceMode(mode) 1145 1146 # start OpenThread 1147 self.__startOpenThread() 1148 self.wait_for_attach_to_the_network(expected_role=eRoleId, 1149 timeout=self.NETWORK_ATTACHMENT_TIMEOUT, 1150 raise_assert=True) 1151 return True 1152 1153 def wait_for_attach_to_the_network(self, expected_role, timeout, raise_assert=False): 1154 start_time = time.time() 1155 1156 while time.time() < start_time + timeout: 1157 time.sleep(0.3) 1158 if self.__isDeviceAttached(): 1159 break 1160 else: 1161 if raise_assert: 1162 raise AssertionError("OT device {} could not attach to the network after {} s of timeout.".format( 1163 self, timeout)) 1164 else: 1165 return False 1166 1167 if self._update_router_status: 1168 self.__updateRouterStatus() 1169 1170 if expected_role == Thread_Device_Role.Router: 1171 while time.time() < start_time + timeout: 1172 time.sleep(0.3) 1173 if self.getDeviceRole() == "router": 1174 break 1175 else: 1176 if raise_assert: 1177 raise AssertionError("OT Router {} could not attach to the network after {} s of timeout.".format( 1178 self, timeout * 2)) 1179 else: 1180 return False 1181 1182 if self.IsBorderRouter: 1183 self._waitBorderRoutingStabilize() 1184 1185 return True 1186 1187 @API 1188 def getNetworkFragmentID(self): 1189 """get current partition id of Thread Network Partition from LeaderData 1190 1191 Returns: 1192 The Thread network Partition Id 1193 """ 1194 if not self.__isOpenThreadRunning(): 1195 return None 1196 1197 leaderData = self.__executeCommand('leaderdata') 1198 return int(leaderData[0].split()[2], 16) 1199 1200 @API 1201 def getParentAddress(self): 1202 """get Thread device's parent extended address and rloc16 short address 1203 1204 Returns: 1205 The extended address of parent in hex format 1206 """ 1207 eui = None 1208 parentInfo = self.__executeCommand('parent') 1209 1210 for line in parentInfo: 1211 if 'Done' in line: 1212 break 1213 elif 'Ext Addr' in line: 1214 eui = line.split()[2] 1215 else: 1216 pass 1217 1218 return int(eui, 16) 1219 1220 @API 1221 def powerDown(self): 1222 """power down the Thread device""" 1223 self.__sendCommand('reset', expectEcho=False) 1224 1225 if not self.IsBorderRouter: 1226 self._disconnect() 1227 self._connect() 1228 1229 self.isPowerDown = True 1230 1231 @API 1232 def powerUp(self): 1233 """power up the Thread device""" 1234 self.isPowerDown = False 1235 1236 if not self.__isOpenThreadRunning(): 1237 if self.deviceRole == Thread_Device_Role.SED: 1238 self.__setPollPeriod(self.__sedPollPeriod) 1239 self.__startOpenThread() 1240 1241 def reset_and_wait_for_connection(self, timeout=3): 1242 print("Waiting after reset timeout: {} s".format(timeout)) 1243 start_time = time.time() 1244 self.__sendCommand('reset', expectEcho=False) 1245 self.isPowerDown = True 1246 1247 while time.time() < start_time + timeout: 1248 time.sleep(0.3) 1249 if not self.IsBorderRouter: 1250 self._disconnect() 1251 self._connect() 1252 try: 1253 self.__executeCommand('state', timeout=0.1) 1254 break 1255 except Exception: 1256 continue 1257 else: 1258 raise AssertionError("Could not connect with OT device {} after reset.".format(self)) 1259 1260 if self.deviceRole == Thread_Device_Role.SED: 1261 self.__setPollPeriod(self.__sedPollPeriod) 1262 1263 @API 1264 def reboot(self): 1265 """reset and rejoin to Thread Network without any timeout 1266 1267 Returns: 1268 True: successful to reset and rejoin the Thread Network 1269 False: fail to reset and rejoin the Thread Network 1270 """ 1271 self.reset_and_wait_for_connection() 1272 self.__startOpenThread() 1273 return self.wait_for_attach_to_the_network(expected_role="", timeout=self.NETWORK_ATTACHMENT_TIMEOUT) 1274 1275 @API 1276 def resetAndRejoin(self, timeout): 1277 """reset and join back Thread Network with a given timeout delay 1278 1279 Args: 1280 timeout: a timeout interval before rejoin Thread Network 1281 1282 Returns: 1283 True: successful to reset and rejoin Thread Network 1284 False: fail to reset and rejoin the Thread Network 1285 """ 1286 self.powerDown() 1287 time.sleep(timeout) 1288 self.powerUp() 1289 return self.wait_for_attach_to_the_network(expected_role="", timeout=self.NETWORK_ATTACHMENT_TIMEOUT) 1290 1291 @API 1292 def ping(self, strDestination, ilength=0, hop_limit=64, timeout=5): 1293 """ send ICMPv6 echo request with a given length/hoplimit to a unicast 1294 destination address 1295 Args: 1296 srcDestination: the unicast destination address of ICMPv6 echo request 1297 ilength: the size of ICMPv6 echo request payload 1298 hop_limit: hop limit 1299 1300 """ 1301 cmd = 'ping %s %s 1 1 %d %d' % (strDestination, str(ilength), hop_limit, timeout) 1302 self.__executeCommand(cmd) 1303 1304 @API 1305 def multicast_Ping(self, destination, length=20): 1306 """send ICMPv6 echo request with a given length to a multicast destination 1307 address 1308 1309 Args: 1310 destination: the multicast destination address of ICMPv6 echo request 1311 length: the size of ICMPv6 echo request payload 1312 """ 1313 cmd = 'ping %s %s' % (destination, str(length)) 1314 self.__sendCommand(cmd) 1315 # wait echo reply 1316 self.sleep(1) 1317 1318 @API 1319 def setPANID(self, xPAN): 1320 """set Thread Network PAN ID 1321 1322 Args: 1323 xPAN: a given PAN ID in hex format 1324 1325 Returns: 1326 True: successful to set the Thread Network PAN ID 1327 False: fail to set the Thread Network PAN ID 1328 """ 1329 panid = '' 1330 if not isinstance(xPAN, str): 1331 panid = str(hex(xPAN)) 1332 1333 cmd = 'panid %s' % panid 1334 datasetCmd = 'dataset panid %s' % panid 1335 self.hasActiveDatasetToCommit = True 1336 return self.__executeCommand(cmd)[-1] == 'Done' and self.__executeCommand(datasetCmd)[-1] == 'Done' 1337 1338 @API 1339 def reset(self): 1340 """factory reset""" 1341 self._deviceBeforeReset() 1342 1343 self.__sendCommand('factoryreset', expectEcho=False) 1344 timeout = 10 1345 1346 start_time = time.time() 1347 while time.time() < start_time + timeout: 1348 time.sleep(0.5) 1349 if not self.IsBorderRouter: 1350 self._disconnect() 1351 self._connect() 1352 try: 1353 self.__executeCommand('state', timeout=0.1) 1354 break 1355 except Exception: 1356 self.__restartAgentService() 1357 time.sleep(2) 1358 self.__sendCommand('factoryreset', expectEcho=False) 1359 time.sleep(0.5) 1360 continue 1361 else: 1362 raise AssertionError("Could not connect with OT device {} after reset.".format(self)) 1363 1364 self.log('factoryreset finished within 10s timeout.') 1365 self._deviceAfterReset() 1366 1367 @API 1368 def removeRouter(self, xRouterId): 1369 """kickoff router with a given router id from the Thread Network 1370 1371 Args: 1372 xRouterId: a given router id in hex format 1373 1374 Returns: 1375 True: successful to remove the router from the Thread Network 1376 False: fail to remove the router from the Thread Network 1377 """ 1378 routerId = self.__convertRlocToRouterId(xRouterId) 1379 1380 if routerId is None: 1381 return False 1382 1383 cmd = 'releaserouterid %s' % routerId 1384 return self.__executeCommand(cmd)[-1] == 'Done' 1385 1386 @API 1387 def setDefaultValues(self): 1388 """set default mandatory Thread Network parameter value""" 1389 # initialize variables 1390 self.networkName = ModuleHelper.Default_NwkName 1391 self.networkKey = ModuleHelper.Default_NwkKey 1392 self.channel = ModuleHelper.Default_Channel 1393 self.channelMask = '0x7fff800' # (0xffff << 11) 1394 self.panId = ModuleHelper.Default_PanId 1395 self.xpanId = ModuleHelper.Default_XpanId 1396 self.meshLocalPrefix = ModuleHelper.Default_MLPrefix 1397 stretchedPSKc = Thread_PBKDF2.get(ModuleHelper.Default_PSKc, ModuleHelper.Default_XpanId, 1398 ModuleHelper.Default_NwkName) 1399 self.pskc = hex(stretchedPSKc).rstrip('L').lstrip('0x') 1400 self.securityPolicySecs = ModuleHelper.Default_SecurityPolicy 1401 self.securityPolicyFlags = 'onrc' 1402 self.activetimestamp = ModuleHelper.Default_ActiveTimestamp 1403 # self.sedPollingRate = ModuleHelper.Default_Harness_SED_Polling_Rate 1404 self.__sedPollPeriod = 3 * 1000 # in milliseconds 1405 self.ssedTimeout = 30 # in seconds 1406 self.cslPeriod = 500 # in milliseconds 1407 self.deviceRole = None 1408 self.provisioningUrl = '' 1409 self.hasActiveDatasetToCommit = False 1410 self.logThread = Queue() 1411 self.logThreadStatus = self.logStatus['stop'] 1412 self.joinCommissionedStatus = self.joinStatus['notstart'] 1413 # indicate Thread device requests full or stable network data 1414 self.networkDataRequirement = '' 1415 # indicate if Thread device experiences a power down event 1416 self.isPowerDown = False 1417 # indicate AddressFilter mode ['disable', 'allowlist', 'denylist'] 1418 self._addressfilterMode = 'disable' 1419 self._addressfilterSet = set() # cache filter entries 1420 # indicate if Thread device is an active commissioner 1421 self.isActiveCommissioner = False 1422 # indicate that the channel has been set, in case the channel was set 1423 # to default when joining network 1424 self.hasSetChannel = False 1425 # indicate whether the default domain prefix is used. 1426 self.__useDefaultDomainPrefix = (self.DeviceCapability == OT12BR_CAPBS) 1427 self.__isUdpOpened = False 1428 self.IsHost = False 1429 1430 # remove stale multicast addresses 1431 if self.IsBorderRouter: 1432 self.stopListeningToAddrAll() 1433 1434 # BBR dataset 1435 if self.DeviceCapability == OT12BR_CAPBS: 1436 self.bbrSeqNum = random.randint(0, 126) # 5.21.4.2 1437 self.bbrMlrTimeout = 3600 1438 self.bbrReRegDelay = 5 1439 1440 # initialize device configuration 1441 self.setMAC(self.mac) 1442 self.__setChannelMask(self.channelMask) 1443 self.__setSecurityPolicy(self.securityPolicySecs, self.securityPolicyFlags) 1444 self.setChannel(self.channel) 1445 self.setPANID(self.panId) 1446 self.setXpanId(self.xpanId) 1447 self.setNetworkName(self.networkName) 1448 self.setNetworkKey(self.networkKey) 1449 self.setMLPrefix(self.meshLocalPrefix) 1450 self.setPSKc(self.pskc) 1451 self.setActiveTimestamp(self.activetimestamp) 1452 1453 @API 1454 def getDeviceConncetionStatus(self): 1455 """check if serial port connection is ready or not""" 1456 return self.deviceConnected 1457 1458 @API 1459 def setPollingRate(self, iPollingRate): 1460 """set data polling rate for sleepy end device 1461 1462 Args: 1463 iPollingRate: data poll period of sleepy end device (in seconds) 1464 1465 Returns: 1466 True: successful to set the data polling rate for sleepy end device 1467 False: fail to set the data polling rate for sleepy end device 1468 """ 1469 iPollingRate = int(iPollingRate * 1000) 1470 1471 if self.__sedPollPeriod != iPollingRate: 1472 if not iPollingRate: 1473 iPollingRate = 0xFFFF # T5.2.1, disable polling 1474 elif iPollingRate < 1: 1475 iPollingRate = 1 # T9.2.13 1476 self.__sedPollPeriod = iPollingRate 1477 1478 # apply immediately 1479 if self.__isOpenThreadRunning(): 1480 return self.__setPollPeriod(self.__sedPollPeriod) 1481 1482 return True 1483 1484 def __setPollPeriod(self, iPollPeriod): 1485 """set data poll period for sleepy end device 1486 1487 Args: 1488 iPollPeriod: data poll period of sleepy end device (in milliseconds) 1489 1490 Returns: 1491 True: successful to set the data poll period for sleepy end device 1492 False: fail to set the data poll period for sleepy end device 1493 """ 1494 cmd = 'pollperiod %d' % iPollPeriod 1495 return self.__executeCommand(cmd)[-1] == 'Done' 1496 1497 @API 1498 def setLinkQuality(self, EUIadr, LinkQuality): 1499 """set custom LinkQualityIn for all receiving messages from the specified EUIadr 1500 1501 Args: 1502 EUIadr: a given extended address 1503 LinkQuality: a given custom link quality 1504 link quality/link margin mapping table 1505 3: 21 - 255 (dB) 1506 2: 11 - 20 (dB) 1507 1: 3 - 9 (dB) 1508 0: 0 - 2 (dB) 1509 1510 Returns: 1511 True: successful to set the link quality 1512 False: fail to set the link quality 1513 """ 1514 # process EUIadr 1515 euiHex = hex(EUIadr) 1516 euiStr = str(euiHex) 1517 euiStr = euiStr.rstrip('L') 1518 address64 = '' 1519 if '0x' in euiStr: 1520 address64 = self.__lstrip0x(euiStr) 1521 # prepend 0 at the beginning 1522 if len(address64) < 16: 1523 address64 = address64.zfill(16) 1524 print(address64) 1525 1526 cmd = 'macfilter rss add-lqi %s %s' % (address64, str(LinkQuality)) 1527 return self.__executeCommand(cmd)[-1] == 'Done' 1528 1529 @API 1530 def setOutBoundLinkQuality(self, LinkQuality): 1531 """set custom LinkQualityIn for all receiving messages from the any address 1532 1533 Args: 1534 LinkQuality: a given custom link quality 1535 link quality/link margin mapping table 1536 3: 21 - 255 (dB) 1537 2: 11 - 20 (dB) 1538 1: 3 - 9 (dB) 1539 0: 0 - 2 (dB) 1540 1541 Returns: 1542 True: successful to set the link quality 1543 False: fail to set the link quality 1544 """ 1545 cmd = 'macfilter rss add-lqi * %s' % str(LinkQuality) 1546 return self.__executeCommand(cmd)[-1] == 'Done' 1547 1548 @API 1549 def removeRouterPrefix(self, prefixEntry): 1550 """remove the configured prefix on a border router 1551 1552 Args: 1553 prefixEntry: a on-mesh prefix entry in IPv6 dotted-quad format 1554 1555 Returns: 1556 True: successful to remove the prefix entry from border router 1557 False: fail to remove the prefix entry from border router 1558 """ 1559 assert (ipaddress.IPv6Network(prefixEntry.decode())) 1560 cmd = 'prefix remove %s/64' % prefixEntry 1561 if self.__executeCommand(cmd)[-1] == 'Done': 1562 # send server data ntf to leader 1563 return self.__executeCommand('netdata register')[-1] == 'Done' 1564 else: 1565 return False 1566 1567 @API 1568 def configBorderRouter( 1569 self, 1570 P_Prefix="fd00:7d03:7d03:7d03::", 1571 P_stable=1, 1572 P_default=1, 1573 P_slaac_preferred=0, 1574 P_Dhcp=0, 1575 P_preference=0, 1576 P_on_mesh=1, 1577 P_nd_dns=0, 1578 P_dp=0, 1579 ): 1580 """configure the border router with a given prefix entry parameters 1581 1582 Args: 1583 P_Prefix: IPv6 prefix that is available on the Thread Network in IPv6 dotted-quad format 1584 P_stable: true if the default router is expected to be stable network data 1585 P_default: true if border router offers the default route for P_Prefix 1586 P_slaac_preferred: true if allowing auto-configure address using P_Prefix 1587 P_Dhcp: is true if border router is a DHCPv6 Agent 1588 P_preference: is two-bit signed integer indicating router preference 1589 P_on_mesh: is true if P_Prefix is considered to be on-mesh 1590 P_nd_dns: is true if border router is able to supply DNS information obtained via ND 1591 1592 Returns: 1593 True: successful to configure the border router with a given prefix entry 1594 False: fail to configure the border router with a given prefix entry 1595 """ 1596 assert (ipaddress.IPv6Network(P_Prefix.decode())) 1597 1598 # turn off default domain prefix if configBorderRouter is called before joining network 1599 if P_dp == 0 and not self.__isOpenThreadRunning(): 1600 self.__useDefaultDomainPrefix = False 1601 1602 parameter = '' 1603 prf = '' 1604 1605 if P_dp: 1606 P_slaac_preferred = 1 1607 1608 if P_slaac_preferred == 1: 1609 parameter += 'p' 1610 parameter += 'a' 1611 1612 if P_stable == 1: 1613 parameter += 's' 1614 1615 if P_default == 1: 1616 parameter += 'r' 1617 1618 if P_Dhcp == 1: 1619 parameter += 'd' 1620 1621 if P_on_mesh == 1: 1622 parameter += 'o' 1623 1624 if P_dp == 1: 1625 assert P_slaac_preferred and P_default and P_on_mesh and P_stable 1626 parameter += 'D' 1627 1628 if P_preference == 1: 1629 prf = 'high' 1630 elif P_preference == 0: 1631 prf = 'med' 1632 elif P_preference == -1: 1633 prf = 'low' 1634 else: 1635 pass 1636 1637 cmd = 'prefix add %s/64 %s %s' % (P_Prefix, parameter, prf) 1638 if self.__executeCommand(cmd)[-1] == 'Done': 1639 # if prefix configured before starting OpenThread stack 1640 # do not send out server data ntf pro-actively 1641 if not self.__isOpenThreadRunning(): 1642 return True 1643 else: 1644 # send server data ntf to leader 1645 return self.__executeCommand('netdata register')[-1] == 'Done' 1646 else: 1647 return False 1648 1649 @watched 1650 def getNetworkData(self): 1651 lines = self.__executeCommand('netdata show') 1652 prefixes, routes, services = [], [], [] 1653 classify = None 1654 1655 for line in lines: 1656 if line == 'Prefixes:': 1657 classify = prefixes 1658 elif line == 'Routes:': 1659 classify = routes 1660 elif line == 'Services:': 1661 classify = services 1662 elif line == 'Done': 1663 classify = None 1664 else: 1665 classify.append(line) 1666 1667 return { 1668 'Prefixes': prefixes, 1669 'Routes': routes, 1670 'Services': services, 1671 } 1672 1673 @API 1674 def setNetworkIDTimeout(self, iNwkIDTimeOut): 1675 """set networkid timeout for Thread device 1676 1677 Args: 1678 iNwkIDTimeOut: a given NETWORK_ID_TIMEOUT 1679 1680 Returns: 1681 True: successful to set NETWORK_ID_TIMEOUT 1682 False: fail to set NETWORK_ID_TIMEOUT 1683 """ 1684 iNwkIDTimeOut /= 1000 1685 cmd = 'networkidtimeout %s' % str(iNwkIDTimeOut) 1686 return self.__executeCommand(cmd)[-1] == 'Done' 1687 1688 @API 1689 def setKeepAliveTimeOut(self, iTimeOut): 1690 """set child timeout for device 1691 1692 Args: 1693 iTimeOut: child timeout for device 1694 1695 Returns: 1696 True: successful to set the child timeout for device 1697 False: fail to set the child timeout for device 1698 """ 1699 cmd = 'childtimeout %d' % iTimeOut 1700 return self.__executeCommand(cmd)[-1] == 'Done' 1701 1702 @API 1703 def setKeySequenceCounter(self, iKeySequenceValue): 1704 """ set the Key sequence counter corresponding to Thread network key 1705 1706 Args: 1707 iKeySequenceValue: key sequence value 1708 1709 Returns: 1710 True: successful to set the key sequence 1711 False: fail to set the key sequence 1712 """ 1713 # avoid key switch guard timer protection for reference device 1714 self.__setKeySwitchGuardTime(0) 1715 1716 cmd = 'keysequence counter %s' % str(iKeySequenceValue) 1717 if self.__executeCommand(cmd)[-1] == 'Done': 1718 self.sleep(1) 1719 return True 1720 else: 1721 return False 1722 1723 @API 1724 def getKeySequenceCounter(self): 1725 """get current Thread Network key sequence""" 1726 keySequence = self.__executeCommand('keysequence counter')[0] 1727 return keySequence 1728 1729 @API 1730 def incrementKeySequenceCounter(self, iIncrementValue=1): 1731 """increment the key sequence with a given value 1732 1733 Args: 1734 iIncrementValue: specific increment value to be added 1735 1736 Returns: 1737 True: successful to increment the key sequence with a given value 1738 False: fail to increment the key sequence with a given value 1739 """ 1740 # avoid key switch guard timer protection for reference device 1741 self.__setKeySwitchGuardTime(0) 1742 currentKeySeq = self.getKeySequenceCounter() 1743 keySequence = int(currentKeySeq, 10) + iIncrementValue 1744 return self.setKeySequenceCounter(keySequence) 1745 1746 @API 1747 def setNetworkDataRequirement(self, eDataRequirement): 1748 """set whether the Thread device requires the full network data 1749 or only requires the stable network data 1750 1751 Args: 1752 eDataRequirement: is true if requiring the full network data 1753 1754 Returns: 1755 True: successful to set the network requirement 1756 """ 1757 if eDataRequirement == Device_Data_Requirement.ALL_DATA: 1758 self.networkDataRequirement = 'n' 1759 return True 1760 1761 @API 1762 def configExternalRouter(self, P_Prefix, P_stable, R_Preference=0): 1763 """configure border router with a given external route prefix entry 1764 1765 Args: 1766 P_Prefix: IPv6 prefix for the route in IPv6 dotted-quad format 1767 P_Stable: is true if the external route prefix is stable network data 1768 R_Preference: a two-bit signed integer indicating Router preference 1769 1: high 1770 0: medium 1771 -1: low 1772 1773 Returns: 1774 True: successful to configure the border router with a given external route prefix 1775 False: fail to configure the border router with a given external route prefix 1776 """ 1777 assert (ipaddress.IPv6Network(P_Prefix.decode())) 1778 prf = '' 1779 stable = '' 1780 if R_Preference == 1: 1781 prf = 'high' 1782 elif R_Preference == 0: 1783 prf = 'med' 1784 elif R_Preference == -1: 1785 prf = 'low' 1786 else: 1787 pass 1788 1789 if P_stable: 1790 stable += 's' 1791 cmd = 'route add %s/64 %s %s' % (P_Prefix, stable, prf) 1792 else: 1793 cmd = 'route add %s/64 %s' % (P_Prefix, prf) 1794 1795 if self.__executeCommand(cmd)[-1] == 'Done': 1796 # send server data ntf to leader 1797 return self.__executeCommand('netdata register')[-1] == 'Done' 1798 1799 @API 1800 def getNeighbouringRouters(self): 1801 """get neighboring routers information 1802 1803 Returns: 1804 neighboring routers' extended address 1805 """ 1806 routerInfo = [] 1807 routerList = self.__executeCommand('router list')[0].split() 1808 1809 if 'Done' in routerList: 1810 return None 1811 1812 for index in routerList: 1813 router = [] 1814 cmd = 'router %s' % index 1815 router = self.__executeCommand(cmd) 1816 1817 for line in router: 1818 if 'Done' in line: 1819 break 1820 # elif 'Rloc' in line: 1821 # rloc16 = line.split()[1] 1822 elif 'Ext Addr' in line: 1823 eui = line.split()[2] 1824 routerInfo.append(int(eui, 16)) 1825 # elif 'LQI In' in line: 1826 # lqi_in = line.split()[1] 1827 # elif 'LQI Out' in line: 1828 # lqi_out = line.split()[1] 1829 else: 1830 pass 1831 1832 return routerInfo 1833 1834 @API 1835 def getChildrenInfo(self): 1836 """get all children information 1837 1838 Returns: 1839 children's extended address 1840 """ 1841 eui = None 1842 rloc16 = None 1843 childrenInfoAll = [] 1844 childrenInfo = {'EUI': 0, 'Rloc16': 0, 'MLEID': ''} 1845 childrenList = self.__executeCommand('child list')[0].split() 1846 1847 if 'Done' in childrenList: 1848 return None 1849 1850 for index in childrenList: 1851 cmd = 'child %s' % index 1852 child = [] 1853 child = self.__executeCommand(cmd) 1854 1855 for line in child: 1856 if 'Done' in line: 1857 break 1858 elif 'Rloc' in line: 1859 rloc16 = line.split()[1] 1860 elif 'Ext Addr' in line: 1861 eui = line.split()[2] 1862 # elif 'Child ID' in line: 1863 # child_id = line.split()[2] 1864 # elif 'Mode' in line: 1865 # mode = line.split()[1] 1866 else: 1867 pass 1868 1869 childrenInfo['EUI'] = int(eui, 16) 1870 childrenInfo['Rloc16'] = int(rloc16, 16) 1871 # children_info['MLEID'] = self.getMLEID() 1872 1873 childrenInfoAll.append(childrenInfo['EUI']) 1874 # childrenInfoAll.append(childrenInfo) 1875 1876 return childrenInfoAll 1877 1878 @API 1879 def setXpanId(self, xPanId): 1880 """set extended PAN ID of Thread Network 1881 1882 Args: 1883 xPanId: extended PAN ID in hex format 1884 1885 Returns: 1886 True: successful to set the extended PAN ID 1887 False: fail to set the extended PAN ID 1888 """ 1889 xpanid = '' 1890 if not isinstance(xPanId, str): 1891 xpanid = self.__convertLongToHex(xPanId, 16) 1892 cmd = 'extpanid %s' % xpanid 1893 datasetCmd = 'dataset extpanid %s' % xpanid 1894 else: 1895 xpanid = xPanId 1896 cmd = 'extpanid %s' % xpanid 1897 datasetCmd = 'dataset extpanid %s' % xpanid 1898 1899 self.xpanId = xpanid 1900 self.hasActiveDatasetToCommit = True 1901 return self.__executeCommand(cmd)[-1] == 'Done' and self.__executeCommand(datasetCmd)[-1] == 'Done' 1902 1903 @API 1904 def getNeighbouringDevices(self): 1905 """gets the neighboring devices' extended address to compute the DUT 1906 extended address automatically 1907 1908 Returns: 1909 A list including extended address of neighboring routers, parent 1910 as well as children 1911 """ 1912 neighbourList = [] 1913 1914 # get parent info 1915 parentAddr = self.getParentAddress() 1916 if parentAddr != 0: 1917 neighbourList.append(parentAddr) 1918 1919 # get ED/SED children info 1920 childNeighbours = self.getChildrenInfo() 1921 if childNeighbours is not None and len(childNeighbours) > 0: 1922 for entry in childNeighbours: 1923 neighbourList.append(entry) 1924 1925 # get neighboring routers info 1926 routerNeighbours = self.getNeighbouringRouters() 1927 if routerNeighbours is not None and len(routerNeighbours) > 0: 1928 for entry in routerNeighbours: 1929 neighbourList.append(entry) 1930 1931 return neighbourList 1932 1933 @API 1934 def setPartationId(self, partationId): 1935 """set Thread Network Partition ID 1936 1937 Args: 1938 partitionId: partition id to be set by leader 1939 1940 Returns: 1941 True: successful to set the Partition ID 1942 False: fail to set the Partition ID 1943 """ 1944 cmd = 'partitionid preferred %s' % (str(hex(partationId)).rstrip('L')) 1945 return self.__executeCommand(cmd)[-1] == 'Done' 1946 1947 @API 1948 def getGUA(self, filterByPrefix=None, eth=False): 1949 """get expected global unicast IPv6 address of Thread device 1950 1951 note: existing filterByPrefix are string of in lowercase. e.g. 1952 '2001' or '2001:0db8:0001:0000". 1953 1954 Args: 1955 filterByPrefix: a given expected global IPv6 prefix to be matched 1956 1957 Returns: 1958 a global IPv6 address 1959 """ 1960 assert not eth 1961 # get global addrs set if multiple 1962 globalAddrs = self.__getGlobal() 1963 1964 if filterByPrefix is None: 1965 return globalAddrs[0] 1966 else: 1967 for fullIp in globalAddrs: 1968 if fullIp.startswith(filterByPrefix): 1969 return fullIp 1970 return str(globalAddrs[0]) 1971 1972 @API 1973 def getShortAddress(self): 1974 """get Rloc16 short address of Thread device""" 1975 return self.getRloc16() 1976 1977 @API 1978 def getULA64(self): 1979 """get mesh local EID of Thread device""" 1980 return self.__executeCommand('ipaddr mleid')[0] 1981 1982 @API 1983 def setMLPrefix(self, sMeshLocalPrefix): 1984 """set mesh local prefix""" 1985 cmd = 'dataset meshlocalprefix %s' % sMeshLocalPrefix 1986 self.hasActiveDatasetToCommit = True 1987 return self.__executeCommand(cmd)[-1] == 'Done' 1988 1989 @API 1990 def getML16(self): 1991 """get mesh local 16 unicast address (Rloc)""" 1992 return self.getRloc() 1993 1994 @API 1995 def downgradeToDevice(self): 1996 pass 1997 1998 @API 1999 def upgradeToRouter(self): 2000 pass 2001 2002 @API 2003 def forceSetSlaac(self, slaacAddress): 2004 """force to set a slaac IPv6 address to Thread interface 2005 2006 Args: 2007 slaacAddress: a slaac IPv6 address to be set 2008 2009 Returns: 2010 True: successful to set slaac address to Thread interface 2011 False: fail to set slaac address to Thread interface 2012 """ 2013 cmd = 'ipaddr add %s' % str(slaacAddress) 2014 return self.__executeCommand(cmd)[-1] == 'Done' 2015 2016 @API 2017 def setSleepyNodePollTime(self): 2018 pass 2019 2020 @API 2021 def enableAutoDUTObjectFlag(self): 2022 """set AutoDUTenable flag""" 2023 self.AutoDUTEnable = True 2024 2025 @API 2026 def getChildTimeoutValue(self): 2027 """get child timeout""" 2028 childTimeout = self.__executeCommand('childtimeout')[0] 2029 return int(childTimeout) 2030 2031 @API 2032 def diagnosticGet(self, strDestinationAddr, listTLV_ids=()): 2033 if not listTLV_ids: 2034 return 2035 2036 if len(listTLV_ids) == 0: 2037 return 2038 2039 cmd = 'networkdiagnostic get %s %s' % ( 2040 strDestinationAddr, 2041 ' '.join([str(tlv) for tlv in listTLV_ids]), 2042 ) 2043 2044 return self.__sendCommand(cmd, expectEcho=False) 2045 2046 @API 2047 def diagnosticReset(self, strDestinationAddr, listTLV_ids=()): 2048 if not listTLV_ids: 2049 return 2050 2051 if len(listTLV_ids) == 0: 2052 return 2053 2054 cmd = 'networkdiagnostic reset %s %s' % ( 2055 strDestinationAddr, 2056 ' '.join([str(tlv) for tlv in listTLV_ids]), 2057 ) 2058 2059 return self.__executeCommand(cmd) 2060 2061 @API 2062 def diagnosticQuery(self, strDestinationAddr, listTLV_ids=()): 2063 self.diagnosticGet(strDestinationAddr, listTLV_ids) 2064 2065 @API 2066 def startNativeCommissioner(self, strPSKc='GRLPASSPHRASE'): 2067 # TODO: Support the whole Native Commissioner functionality 2068 # Currently it only aims to trigger a Discovery Request message to pass 2069 # Certification test 5.8.4 2070 self.__executeCommand('ifconfig up') 2071 cmd = 'joiner start %s' % (strPSKc) 2072 return self.__executeCommand(cmd)[-1] == 'Done' 2073 2074 @API 2075 def startExternalCommissioner(self, baAddr, baPort): 2076 """Start external commissioner 2077 Args: 2078 baAddr: A string represents the border agent address. 2079 baPort: An integer represents the border agent port. 2080 Returns: 2081 A boolean indicates whether this function succeed. 2082 """ 2083 if self.externalCommissioner is None: 2084 config = commissioner.Configuration() 2085 config.isCcmMode = False 2086 config.domainName = OpenThreadTHCI.DOMAIN_NAME 2087 config.pskc = bytearray.fromhex(self.pskc) 2088 2089 self.externalCommissioner = OTCommissioner(config, self) 2090 2091 if not self.externalCommissioner.isActive(): 2092 self.externalCommissioner.start(baAddr, baPort) 2093 2094 if not self.externalCommissioner.isActive(): 2095 raise commissioner.Error("external commissioner is not active") 2096 2097 return True 2098 2099 @API 2100 def stopExternalCommissioner(self): 2101 """Stop external commissioner 2102 Returns: 2103 A boolean indicates whether this function succeed. 2104 """ 2105 if self.externalCommissioner is not None: 2106 self.externalCommissioner.stop() 2107 return not self.externalCommissioner.isActive() 2108 2109 @API 2110 def startCollapsedCommissioner(self, role=Thread_Device_Role.Leader): 2111 """start Collapsed Commissioner 2112 2113 Returns: 2114 True: successful to start Commissioner 2115 False: fail to start Commissioner 2116 """ 2117 if self.__startOpenThread(): 2118 self.wait_for_attach_to_the_network(expected_role=self.deviceRole, 2119 timeout=self.NETWORK_ATTACHMENT_TIMEOUT, 2120 raise_assert=True) 2121 cmd = 'commissioner start' 2122 if self.__executeCommand(cmd)[-1] == 'Done': 2123 self.isActiveCommissioner = True 2124 self.sleep(20) # time for petition process 2125 return True 2126 return False 2127 2128 @API 2129 def setJoinKey(self, strPSKc): 2130 pass 2131 2132 @API 2133 def scanJoiner(self, xEUI='*', strPSKd='THREADJPAKETEST'): 2134 """scan Joiner 2135 2136 Args: 2137 xEUI: Joiner's EUI-64 2138 strPSKd: Joiner's PSKd for commissioning 2139 2140 Returns: 2141 True: successful to add Joiner's steering data 2142 False: fail to add Joiner's steering data 2143 """ 2144 self.log("scanJoiner on channel %s", self.getChannel()) 2145 2146 # long timeout value to avoid automatic joiner removal (in seconds) 2147 timeout = 500 2148 2149 if not isinstance(xEUI, str): 2150 eui64 = self.__convertLongToHex(xEUI, 16) 2151 else: 2152 eui64 = xEUI 2153 2154 strPSKd = self.__normalizePSKd(strPSKd) 2155 2156 cmd = 'commissioner joiner add %s %s %s' % ( 2157 self._deviceEscapeEscapable(eui64), 2158 strPSKd, 2159 str(timeout), 2160 ) 2161 2162 if self.__executeCommand(cmd)[-1] == 'Done': 2163 if self.logThreadStatus == self.logStatus['stop']: 2164 self.logThread = ThreadRunner.run(target=self.__readCommissioningLogs, args=(120,)) 2165 return True 2166 else: 2167 return False 2168 2169 @staticmethod 2170 def __normalizePSKd(strPSKd): 2171 return strPSKd.upper().replace('I', '1').replace('O', '0').replace('Q', '0').replace('Z', '2') 2172 2173 @API 2174 def setProvisioningUrl(self, strURL='grl.com'): 2175 """set provisioning Url 2176 2177 Args: 2178 strURL: Provisioning Url string 2179 2180 Returns: 2181 True: successful to set provisioning Url 2182 False: fail to set provisioning Url 2183 """ 2184 self.provisioningUrl = strURL 2185 if self.deviceRole == Thread_Device_Role.Commissioner: 2186 cmd = 'commissioner provisioningurl %s' % (strURL) 2187 return self.__executeCommand(cmd)[-1] == 'Done' 2188 return True 2189 2190 @API 2191 def allowCommission(self): 2192 """start commissioner candidate petition process 2193 2194 Returns: 2195 True: successful to start commissioner candidate petition process 2196 False: fail to start commissioner candidate petition process 2197 """ 2198 cmd = 'commissioner start' 2199 if self.__executeCommand(cmd)[-1] == 'Done': 2200 self.isActiveCommissioner = True 2201 # time for petition process and at least one keep alive 2202 self.sleep(3) 2203 return True 2204 else: 2205 return False 2206 2207 @API 2208 def joinCommissioned(self, strPSKd='THREADJPAKETEST', waitTime=20): 2209 """start joiner 2210 2211 Args: 2212 strPSKd: Joiner's PSKd 2213 2214 Returns: 2215 True: successful to start joiner 2216 False: fail to start joiner 2217 """ 2218 self.log("joinCommissioned on channel %s", self.getChannel()) 2219 2220 if self.deviceRole in [ 2221 Thread_Device_Role.Leader, 2222 Thread_Device_Role.Router, 2223 Thread_Device_Role.REED, 2224 ]: 2225 self.__setRouterSelectionJitter(1) 2226 self.__executeCommand('ifconfig up') 2227 strPSKd = self.__normalizePSKd(strPSKd) 2228 cmd = 'joiner start %s %s' % (strPSKd, self.provisioningUrl) 2229 if self.__executeCommand(cmd)[-1] == 'Done': 2230 maxDuration = 150 # seconds 2231 self.joinCommissionedStatus = self.joinStatus['ongoing'] 2232 2233 if self.logThreadStatus == self.logStatus['stop']: 2234 self.logThread = ThreadRunner.run(target=self.__readCommissioningLogs, args=(maxDuration,)) 2235 2236 t_end = time.time() + maxDuration 2237 while time.time() < t_end: 2238 if self.joinCommissionedStatus == self.joinStatus['succeed']: 2239 break 2240 elif self.joinCommissionedStatus == self.joinStatus['failed']: 2241 return False 2242 2243 self.sleep(1) 2244 2245 self.setMAC(self.mac) 2246 self.__executeCommand('thread start') 2247 self.wait_for_attach_to_the_network(expected_role=self.deviceRole, 2248 timeout=self.NETWORK_ATTACHMENT_TIMEOUT, 2249 raise_assert=True) 2250 return True 2251 else: 2252 return False 2253 2254 @API 2255 def getCommissioningLogs(self): 2256 """get Commissioning logs 2257 2258 Returns: 2259 Commissioning logs 2260 """ 2261 rawLogs = self.logThread.get() 2262 ProcessedLogs = [] 2263 payload = [] 2264 2265 while not rawLogs.empty(): 2266 rawLogEach = rawLogs.get() 2267 if '[THCI]' not in rawLogEach: 2268 continue 2269 2270 EncryptedPacket = PlatformDiagnosticPacket() 2271 infoList = rawLogEach.split('[THCI]')[1].split(']')[0].split('|') 2272 for eachInfo in infoList: 2273 info = eachInfo.split('=') 2274 infoType = info[0].strip() 2275 infoValue = info[1].strip() 2276 if 'direction' in infoType: 2277 EncryptedPacket.Direction = (PlatformDiagnosticPacket_Direction.IN 2278 if 'recv' in infoValue else PlatformDiagnosticPacket_Direction.OUT if 2279 'send' in infoValue else PlatformDiagnosticPacket_Direction.UNKNOWN) 2280 elif 'type' in infoType: 2281 EncryptedPacket.Type = (PlatformDiagnosticPacket_Type.JOIN_FIN_req if 'JOIN_FIN.req' in infoValue 2282 else PlatformDiagnosticPacket_Type.JOIN_FIN_rsp if 'JOIN_FIN.rsp' 2283 in infoValue else PlatformDiagnosticPacket_Type.JOIN_ENT_req if 2284 'JOIN_ENT.ntf' in infoValue else PlatformDiagnosticPacket_Type.JOIN_ENT_rsp 2285 if 'JOIN_ENT.rsp' in infoValue else PlatformDiagnosticPacket_Type.UNKNOWN) 2286 elif 'len' in infoType: 2287 bytesInEachLine = 16 2288 EncryptedPacket.TLVsLength = int(infoValue) 2289 payloadLineCount = (int(infoValue) + bytesInEachLine - 1) / bytesInEachLine 2290 while payloadLineCount > 0: 2291 payloadLineCount = payloadLineCount - 1 2292 payloadLine = rawLogs.get() 2293 payloadSplit = payloadLine.split('|') 2294 for block in range(1, 3): 2295 payloadBlock = payloadSplit[block] 2296 payloadValues = payloadBlock.split(' ') 2297 for num in range(1, 9): 2298 if '..' not in payloadValues[num]: 2299 payload.append(int(payloadValues[num], 16)) 2300 2301 EncryptedPacket.TLVs = (PlatformPackets.read(EncryptedPacket.Type, payload) 2302 if payload != [] else []) 2303 2304 ProcessedLogs.append(EncryptedPacket) 2305 return ProcessedLogs 2306 2307 @API 2308 def MGMT_ED_SCAN( 2309 self, 2310 sAddr, 2311 xCommissionerSessionId, 2312 listChannelMask, 2313 xCount, 2314 xPeriod, 2315 xScanDuration, 2316 ): 2317 """send MGMT_ED_SCAN message to a given destinaition. 2318 2319 Args: 2320 sAddr: IPv6 destination address for this message 2321 xCommissionerSessionId: commissioner session id 2322 listChannelMask: a channel array to indicate which channels to be scaned 2323 xCount: number of IEEE 802.15.4 ED Scans (milliseconds) 2324 xPeriod: Period between successive IEEE802.15.4 ED Scans (milliseconds) 2325 xScanDuration: ScanDuration when performing an IEEE 802.15.4 ED Scan (milliseconds) 2326 2327 Returns: 2328 True: successful to send MGMT_ED_SCAN message. 2329 False: fail to send MGMT_ED_SCAN message 2330 """ 2331 channelMask = '0x' + self.__convertLongToHex(self.__convertChannelMask(listChannelMask)) 2332 cmd = 'commissioner energy %s %s %s %s %s' % ( 2333 channelMask, 2334 xCount, 2335 xPeriod, 2336 xScanDuration, 2337 sAddr, 2338 ) 2339 return self.__executeCommand(cmd)[-1] == 'Done' 2340 2341 @API 2342 def MGMT_PANID_QUERY(self, sAddr, xCommissionerSessionId, listChannelMask, xPanId): 2343 """send MGMT_PANID_QUERY message to a given destination 2344 2345 Args: 2346 xPanId: a given PAN ID to check the conflicts 2347 2348 Returns: 2349 True: successful to send MGMT_PANID_QUERY message. 2350 False: fail to send MGMT_PANID_QUERY message. 2351 """ 2352 panid = '' 2353 channelMask = '0x' + self.__convertLongToHex(self.__convertChannelMask(listChannelMask)) 2354 2355 if not isinstance(xPanId, str): 2356 panid = str(hex(xPanId)) 2357 2358 cmd = 'commissioner panid %s %s %s' % (panid, channelMask, sAddr) 2359 return self.__executeCommand(cmd)[-1] == 'Done' 2360 2361 @API 2362 def MGMT_ANNOUNCE_BEGIN(self, sAddr, xCommissionerSessionId, listChannelMask, xCount, xPeriod): 2363 """send MGMT_ANNOUNCE_BEGIN message to a given destination 2364 2365 Returns: 2366 True: successful to send MGMT_ANNOUNCE_BEGIN message. 2367 False: fail to send MGMT_ANNOUNCE_BEGIN message. 2368 """ 2369 channelMask = '0x' + self.__convertLongToHex(self.__convertChannelMask(listChannelMask)) 2370 cmd = 'commissioner announce %s %s %s %s' % ( 2371 channelMask, 2372 xCount, 2373 xPeriod, 2374 sAddr, 2375 ) 2376 return self.__executeCommand(cmd)[-1] == 'Done' 2377 2378 @API 2379 def MGMT_ACTIVE_GET(self, Addr='', TLVs=()): 2380 """send MGMT_ACTIVE_GET command 2381 2382 Returns: 2383 True: successful to send MGMT_ACTIVE_GET 2384 False: fail to send MGMT_ACTIVE_GET 2385 """ 2386 cmd = 'dataset mgmtgetcommand active' 2387 2388 if Addr != '': 2389 cmd += ' address ' 2390 cmd += Addr 2391 2392 if len(TLVs) != 0: 2393 tlvs = ''.join('%02x' % tlv for tlv in TLVs) 2394 cmd += ' -x ' 2395 cmd += tlvs 2396 2397 return self.__executeCommand(cmd)[-1] == 'Done' 2398 2399 @API 2400 def MGMT_ACTIVE_SET( 2401 self, 2402 sAddr='', 2403 xCommissioningSessionId=None, 2404 listActiveTimestamp=None, 2405 listChannelMask=None, 2406 xExtendedPanId=None, 2407 sNetworkName=None, 2408 sPSKc=None, 2409 listSecurityPolicy=None, 2410 xChannel=None, 2411 sMeshLocalPrefix=None, 2412 xMasterKey=None, 2413 xPanId=None, 2414 xTmfPort=None, 2415 xSteeringData=None, 2416 xBorderRouterLocator=None, 2417 BogusTLV=None, 2418 xDelayTimer=None, 2419 ): 2420 """send MGMT_ACTIVE_SET command 2421 2422 Returns: 2423 True: successful to send MGMT_ACTIVE_SET 2424 False: fail to send MGMT_ACTIVE_SET 2425 """ 2426 cmd = 'dataset mgmtsetcommand active' 2427 2428 if listActiveTimestamp is not None: 2429 cmd += ' activetimestamp ' 2430 cmd += str(listActiveTimestamp[0]) 2431 2432 if xExtendedPanId is not None: 2433 cmd += ' extpanid ' 2434 xpanid = self.__convertLongToHex(xExtendedPanId, 16) 2435 2436 cmd += xpanid 2437 2438 if sNetworkName is not None: 2439 cmd += ' networkname ' 2440 cmd += self._deviceEscapeEscapable(str(sNetworkName)) 2441 2442 if xChannel is not None: 2443 cmd += ' channel ' 2444 cmd += str(xChannel) 2445 2446 if sMeshLocalPrefix is not None: 2447 cmd += ' localprefix ' 2448 cmd += str(sMeshLocalPrefix) 2449 2450 if xMasterKey is not None: 2451 cmd += ' networkkey ' 2452 key = self.__convertLongToHex(xMasterKey, 32) 2453 2454 cmd += key 2455 2456 if xPanId is not None: 2457 cmd += ' panid ' 2458 cmd += str(xPanId) 2459 2460 if listChannelMask is not None: 2461 cmd += ' channelmask ' 2462 cmd += '0x' + self.__convertLongToHex(self.__convertChannelMask(listChannelMask)) 2463 2464 if (sPSKc is not None or listSecurityPolicy is not None or xCommissioningSessionId is not None or 2465 xTmfPort is not None or xSteeringData is not None or xBorderRouterLocator is not None or 2466 BogusTLV is not None): 2467 cmd += ' -x ' 2468 2469 if sPSKc is not None: 2470 cmd += '0410' 2471 stretchedPskc = Thread_PBKDF2.get( 2472 sPSKc, 2473 ModuleHelper.Default_XpanId, 2474 ModuleHelper.Default_NwkName, 2475 ) 2476 pskc = '%x' % stretchedPskc 2477 2478 if len(pskc) < 32: 2479 pskc = pskc.zfill(32) 2480 2481 cmd += pskc 2482 2483 if listSecurityPolicy is not None: 2484 if self.DeviceCapability == DevCapb.V1_1: 2485 cmd += '0c03' 2486 else: 2487 cmd += '0c04' 2488 2489 rotationTime = 0 2490 policyBits = 0 2491 2492 # previous passing way listSecurityPolicy=[True, True, 3600, 2493 # False, False, True] 2494 if len(listSecurityPolicy) == 6: 2495 rotationTime = listSecurityPolicy[2] 2496 2497 # the last three reserved bits must be 1 2498 policyBits = 0b00000111 2499 2500 if listSecurityPolicy[0]: 2501 policyBits = policyBits | 0b10000000 2502 if listSecurityPolicy[1]: 2503 policyBits = policyBits | 0b01000000 2504 if listSecurityPolicy[3]: 2505 policyBits = policyBits | 0b00100000 2506 if listSecurityPolicy[4]: 2507 policyBits = policyBits | 0b00010000 2508 if listSecurityPolicy[5]: 2509 policyBits = policyBits | 0b00001000 2510 else: 2511 # new passing way listSecurityPolicy=[3600, 0b11001111] 2512 rotationTime = listSecurityPolicy[0] 2513 # bit order 2514 if len(listSecurityPolicy) > 2: 2515 policyBits = listSecurityPolicy[2] << 8 | listSecurityPolicy[1] 2516 else: 2517 policyBits = listSecurityPolicy[1] 2518 2519 policy = str(hex(rotationTime))[2:] 2520 2521 if len(policy) < 4: 2522 policy = policy.zfill(4) 2523 2524 cmd += policy 2525 2526 flags0 = ('%x' % (policyBits & 0x00ff)).ljust(2, '0') 2527 cmd += flags0 2528 2529 if self.DeviceCapability != DevCapb.V1_1: 2530 flags1 = ('%x' % ((policyBits & 0xff00) >> 8)).ljust(2, '0') 2531 cmd += flags1 2532 2533 if xCommissioningSessionId is not None: 2534 cmd += '0b02' 2535 sessionid = str(hex(xCommissioningSessionId))[2:] 2536 2537 if len(sessionid) < 4: 2538 sessionid = sessionid.zfill(4) 2539 2540 cmd += sessionid 2541 2542 if xBorderRouterLocator is not None: 2543 cmd += '0902' 2544 locator = str(hex(xBorderRouterLocator))[2:] 2545 2546 if len(locator) < 4: 2547 locator = locator.zfill(4) 2548 2549 cmd += locator 2550 2551 if xSteeringData is not None: 2552 steeringData = self.__convertLongToHex(xSteeringData) 2553 cmd += '08' + str(len(steeringData) / 2).zfill(2) 2554 cmd += steeringData 2555 2556 if BogusTLV is not None: 2557 cmd += '8202aa55' 2558 2559 return self.__executeCommand(cmd)[-1] == 'Done' 2560 2561 @API 2562 def MGMT_PENDING_GET(self, Addr='', TLVs=()): 2563 """send MGMT_PENDING_GET command 2564 2565 Returns: 2566 True: successful to send MGMT_PENDING_GET 2567 False: fail to send MGMT_PENDING_GET 2568 """ 2569 cmd = 'dataset mgmtgetcommand pending' 2570 2571 if Addr != '': 2572 cmd += ' address ' 2573 cmd += Addr 2574 2575 if len(TLVs) != 0: 2576 tlvs = ''.join('%02x' % tlv for tlv in TLVs) 2577 cmd += ' -x ' 2578 cmd += tlvs 2579 2580 return self.__executeCommand(cmd)[-1] == 'Done' 2581 2582 @API 2583 def MGMT_PENDING_SET( 2584 self, 2585 sAddr='', 2586 xCommissionerSessionId=None, 2587 listPendingTimestamp=None, 2588 listActiveTimestamp=None, 2589 xDelayTimer=None, 2590 xChannel=None, 2591 xPanId=None, 2592 xMasterKey=None, 2593 sMeshLocalPrefix=None, 2594 sNetworkName=None, 2595 ): 2596 """send MGMT_PENDING_SET command 2597 2598 Returns: 2599 True: successful to send MGMT_PENDING_SET 2600 False: fail to send MGMT_PENDING_SET 2601 """ 2602 cmd = 'dataset mgmtsetcommand pending' 2603 2604 if listPendingTimestamp is not None: 2605 cmd += ' pendingtimestamp ' 2606 cmd += str(listPendingTimestamp[0]) 2607 2608 if listActiveTimestamp is not None: 2609 cmd += ' activetimestamp ' 2610 cmd += str(listActiveTimestamp[0]) 2611 2612 if xDelayTimer is not None: 2613 cmd += ' delaytimer ' 2614 cmd += str(xDelayTimer) 2615 # cmd += ' delaytimer 3000000' 2616 2617 if xChannel is not None: 2618 cmd += ' channel ' 2619 cmd += str(xChannel) 2620 2621 if xPanId is not None: 2622 cmd += ' panid ' 2623 cmd += str(xPanId) 2624 2625 if xMasterKey is not None: 2626 cmd += ' networkkey ' 2627 key = self.__convertLongToHex(xMasterKey, 32) 2628 2629 cmd += key 2630 2631 if sMeshLocalPrefix is not None: 2632 cmd += ' localprefix ' 2633 cmd += str(sMeshLocalPrefix) 2634 2635 if sNetworkName is not None: 2636 cmd += ' networkname ' 2637 cmd += self._deviceEscapeEscapable(str(sNetworkName)) 2638 2639 if xCommissionerSessionId is not None: 2640 cmd += ' -x ' 2641 cmd += '0b02' 2642 sessionid = str(hex(xCommissionerSessionId))[2:] 2643 2644 if len(sessionid) < 4: 2645 sessionid = sessionid.zfill(4) 2646 2647 cmd += sessionid 2648 2649 return self.__executeCommand(cmd)[-1] == 'Done' 2650 2651 @API 2652 def MGMT_COMM_GET(self, Addr='ff02::1', TLVs=()): 2653 """send MGMT_COMM_GET command 2654 2655 Returns: 2656 True: successful to send MGMT_COMM_GET 2657 False: fail to send MGMT_COMM_GET 2658 """ 2659 cmd = 'commissioner mgmtget' 2660 2661 if len(TLVs) != 0: 2662 tlvs = ''.join('%02x' % tlv for tlv in TLVs) 2663 cmd += ' -x ' 2664 cmd += tlvs 2665 2666 return self.__executeCommand(cmd)[-1] == 'Done' 2667 2668 @API 2669 def MGMT_COMM_SET( 2670 self, 2671 Addr='ff02::1', 2672 xCommissionerSessionID=None, 2673 xSteeringData=None, 2674 xBorderRouterLocator=None, 2675 xChannelTlv=None, 2676 ExceedMaxPayload=False, 2677 ): 2678 """send MGMT_COMM_SET command 2679 2680 Returns: 2681 True: successful to send MGMT_COMM_SET 2682 False: fail to send MGMT_COMM_SET 2683 """ 2684 cmd = 'commissioner mgmtset' 2685 2686 if xCommissionerSessionID is not None: 2687 # use assigned session id 2688 cmd += ' sessionid ' 2689 cmd += str(xCommissionerSessionID) 2690 elif xCommissionerSessionID is None: 2691 # use original session id 2692 if self.isActiveCommissioner is True: 2693 cmd += ' sessionid ' 2694 cmd += self.__getCommissionerSessionId() 2695 else: 2696 pass 2697 2698 if xSteeringData is not None: 2699 cmd += ' steeringdata ' 2700 cmd += str(hex(xSteeringData)[2:]) 2701 2702 if xBorderRouterLocator is not None: 2703 cmd += ' locator ' 2704 cmd += str(hex(xBorderRouterLocator)) 2705 2706 if xChannelTlv is not None: 2707 cmd += ' -x ' 2708 cmd += '000300' + '%04x' % xChannelTlv 2709 2710 return self.__executeCommand(cmd)[-1] == 'Done' 2711 2712 @API 2713 def setActiveDataset(self, listActiveDataset=()): 2714 # Unused by the scripts 2715 pass 2716 2717 @API 2718 def setCommisionerMode(self): 2719 # Unused by the scripts 2720 pass 2721 2722 @API 2723 def setPSKc(self, strPSKc): 2724 cmd = 'dataset pskc %s' % strPSKc 2725 self.hasActiveDatasetToCommit = True 2726 return self.__executeCommand(cmd)[-1] == 'Done' 2727 2728 @API 2729 def setActiveTimestamp(self, xActiveTimestamp): 2730 self.activetimestamp = xActiveTimestamp 2731 self.hasActiveDatasetToCommit = True 2732 cmd = 'dataset activetimestamp %s' % str(xActiveTimestamp) 2733 return self.__executeCommand(cmd)[-1] == 'Done' 2734 2735 @API 2736 def setUdpJoinerPort(self, portNumber): 2737 """set Joiner UDP Port 2738 2739 Args: 2740 portNumber: Joiner UDP Port number 2741 2742 Returns: 2743 True: successful to set Joiner UDP Port 2744 False: fail to set Joiner UDP Port 2745 """ 2746 cmd = 'joinerport %d' % portNumber 2747 return self.__executeCommand(cmd)[-1] == 'Done' 2748 2749 @API 2750 def commissionerUnregister(self): 2751 """stop commissioner 2752 2753 Returns: 2754 True: successful to stop commissioner 2755 False: fail to stop commissioner 2756 """ 2757 cmd = 'commissioner stop' 2758 return self.__executeCommand(cmd)[-1] == 'Done' 2759 2760 @API 2761 def sendBeacons(self, sAddr, xCommissionerSessionId, listChannelMask, xPanId): 2762 self.__sendCommand('scan', expectEcho=False) 2763 2764 @API 2765 def updateRouterStatus(self): 2766 """force update to router as if there is child id request""" 2767 self._update_router_status = True 2768 2769 @API 2770 def __updateRouterStatus(self): 2771 cmd = 'state' 2772 while True: 2773 state = self.__executeCommand(cmd)[0] 2774 if state == 'detached': 2775 continue 2776 elif state == 'child': 2777 break 2778 else: 2779 return False 2780 2781 cmd = 'state router' 2782 return self.__executeCommand(cmd)[-1] == 'Done' 2783 2784 @API 2785 def setRouterThresholdValues(self, upgradeThreshold, downgradeThreshold): 2786 self.__setRouterUpgradeThreshold(upgradeThreshold) 2787 self.__setRouterDowngradeThreshold(downgradeThreshold) 2788 2789 @API 2790 def setMinDelayTimer(self, iSeconds): 2791 cmd = 'delaytimermin %s' % iSeconds 2792 return self.__executeCommand(cmd)[-1] == 'Done' 2793 2794 @API 2795 def ValidateDeviceFirmware(self): 2796 assert not self.IsBorderRouter, "Method not expected to be used with border router devices" 2797 2798 if self.DeviceCapability == OT11_CAPBS: 2799 return OT11_VERSION in self.UIStatusMsg 2800 elif self.DeviceCapability == OT12_CAPBS: 2801 return OT12_VERSION in self.UIStatusMsg 2802 elif self.DeviceCapability == OT13_CAPBS: 2803 return OT13_VERSION in self.UIStatusMsg 2804 else: 2805 return False 2806 2807 @API 2808 def setBbrDataset(self, SeqNumInc=False, SeqNum=None, MlrTimeout=None, ReRegDelay=None): 2809 """ set BBR Dataset 2810 2811 Args: 2812 SeqNumInc: Increase `SeqNum` by 1 if True. 2813 SeqNum: Set `SeqNum` to a given value if not None. 2814 MlrTimeout: Set `MlrTimeout` to a given value. 2815 ReRegDelay: Set `ReRegDelay` to a given value. 2816 2817 MUST NOT set SeqNumInc to True and SeqNum to non-None value at the same time. 2818 2819 Returns: 2820 True: successful to set BBR Dataset 2821 False: fail to set BBR Dataset 2822 """ 2823 assert not (SeqNumInc and SeqNum is not None), "Must not specify both SeqNumInc and SeqNum" 2824 2825 if (MlrTimeout and MlrTimeout != self.bbrMlrTimeout) or (ReRegDelay and ReRegDelay != self.bbrReRegDelay): 2826 if SeqNum is None: 2827 SeqNumInc = True 2828 2829 if SeqNumInc: 2830 if self.bbrSeqNum in (126, 127): 2831 self.bbrSeqNum = 0 2832 elif self.bbrSeqNum in (254, 255): 2833 self.bbrSeqNum = 128 2834 else: 2835 self.bbrSeqNum = (self.bbrSeqNum + 1) % 256 2836 else: 2837 self.bbrSeqNum = SeqNum 2838 2839 return self.__configBbrDataset(SeqNum=self.bbrSeqNum, MlrTimeout=MlrTimeout, ReRegDelay=ReRegDelay) 2840 2841 def __configBbrDataset(self, SeqNum=None, MlrTimeout=None, ReRegDelay=None): 2842 if MlrTimeout is not None and ReRegDelay is None: 2843 ReRegDelay = self.bbrReRegDelay 2844 2845 cmd = 'bbr config' 2846 if SeqNum is not None: 2847 cmd += ' seqno %d' % SeqNum 2848 if ReRegDelay is not None: 2849 cmd += ' delay %d' % ReRegDelay 2850 if MlrTimeout is not None: 2851 cmd += ' timeout %d' % MlrTimeout 2852 ret = self.__executeCommand(cmd)[-1] == 'Done' 2853 2854 if SeqNum is not None: 2855 self.bbrSeqNum = SeqNum 2856 2857 if MlrTimeout is not None: 2858 self.bbrMlrTimeout = MlrTimeout 2859 2860 if ReRegDelay is not None: 2861 self.bbrReRegDelay = ReRegDelay 2862 2863 self.__executeCommand('netdata register') 2864 2865 return ret 2866 2867 # Low power THCI 2868 @API 2869 def setCSLtout(self, tout=30): 2870 self.ssedTimeout = tout 2871 cmd = 'csl timeout %u' % self.ssedTimeout 2872 return self.__executeCommand(cmd)[-1] == 'Done' 2873 2874 @API 2875 def setCSLchannel(self, ch=11): 2876 cmd = 'csl channel %u' % ch 2877 return self.__executeCommand(cmd)[-1] == 'Done' 2878 2879 @API 2880 def setCSLperiod(self, period=500): 2881 """set Csl Period 2882 Args: 2883 period: csl period in ms 2884 2885 note: OT command 'csl period' accepts parameter in unit of 10 symbols, 2886 period is converted from unit ms to ten symbols (160us per 10 symbols). 2887 2888 """ 2889 cmd = 'csl period %u' % (period * 6.25) 2890 return self.__executeCommand(cmd)[-1] == 'Done' 2891 2892 @staticmethod 2893 def getForwardSeriesFlagsFromHexOrStr(flags): 2894 hexFlags = int(flags, 16) if isinstance(flags, str) else flags 2895 strFlags = '' 2896 if hexFlags == 0: 2897 strFlags = 'X' 2898 else: 2899 if hexFlags & 0x1 != 0: 2900 strFlags += 'l' 2901 if hexFlags & 0x2 != 0: 2902 strFlags += 'd' 2903 if hexFlags & 0x4 != 0: 2904 strFlags += 'r' 2905 if hexFlags & 0x8 != 0: 2906 strFlags += 'a' 2907 2908 return strFlags 2909 2910 @staticmethod 2911 def mapMetricsHexToChar(metrics): 2912 metricsFlagMap = { 2913 0x40: 'p', 2914 0x09: 'q', 2915 0x0a: 'm', 2916 0x0b: 'r', 2917 } 2918 metricsReservedFlagMap = {0x11: 'q', 0x12: 'm', 0x13: 'r'} 2919 if metricsFlagMap.get(metrics): 2920 return metricsFlagMap.get(metrics), False 2921 elif metricsReservedFlagMap.get(metrics): 2922 return metricsReservedFlagMap.get(metrics), True 2923 else: 2924 logging.warning("Not found flag mapping for given metrics: {}".format(metrics)) 2925 return '', False 2926 2927 @staticmethod 2928 def getMetricsFlagsFromHexStr(metrics): 2929 strMetrics = '' 2930 reserved_flag = '' 2931 2932 if metrics.startswith('0x'): 2933 metrics = metrics[2:] 2934 hexMetricsArray = bytearray.fromhex(metrics) 2935 2936 for metric in hexMetricsArray: 2937 metric_flag, has_reserved_flag = OpenThreadTHCI.mapMetricsHexToChar(metric) 2938 strMetrics += metric_flag 2939 if has_reserved_flag: 2940 reserved_flag = ' r' 2941 2942 return strMetrics + reserved_flag 2943 2944 @API 2945 def LinkMetricsSingleReq(self, dst_addr, metrics): 2946 cmd = 'linkmetrics query %s single %s' % (dst_addr, self.getMetricsFlagsFromHexStr(metrics)) 2947 return self.__executeCommand(cmd)[-1] == 'Done' 2948 2949 @API 2950 def LinkMetricsMgmtReq(self, dst_addr, type_, flags, metrics, series_id): 2951 cmd = 'linkmetrics mgmt %s ' % dst_addr 2952 if type_ == 'FWD': 2953 cmd += 'forward %d %s' % (series_id, self.getForwardSeriesFlagsFromHexOrStr(flags)) 2954 if flags != 0: 2955 cmd += ' %s' % (self.getMetricsFlagsFromHexStr(metrics)) 2956 elif type_ == 'ENH': 2957 cmd += 'enhanced-ack' 2958 if flags != 0: 2959 cmd += ' register %s' % (self.getMetricsFlagsFromHexStr(metrics)) 2960 else: 2961 cmd += ' clear' 2962 return self.__executeCommand(cmd)[-1] == 'Done' 2963 2964 @API 2965 def LinkMetricsGetReport(self, dst_addr, series_id): 2966 cmd = 'linkmetrics query %s forward %d' % (dst_addr, series_id) 2967 return self.__executeCommand(cmd)[-1] == 'Done' 2968 2969 # TODO: Series Id is not in this API. 2970 @API 2971 def LinkMetricsSendProbe(self, dst_addr, ack=True, size=0): 2972 cmd = 'linkmetrics probe %s %d' % (dst_addr, size) 2973 return self.__executeCommand(cmd)[-1] == 'Done' 2974 2975 @API 2976 def setTxPower(self, level): 2977 cmd = 'txpower ' 2978 if level == 'HIGH': 2979 cmd += '127' 2980 elif level == 'MEDIUM': 2981 cmd += '0' 2982 elif level == 'LOW': 2983 cmd += '-128' 2984 else: 2985 print('wrong Tx Power level') 2986 return self.__executeCommand(cmd)[-1] == 'Done' 2987 2988 @API 2989 def sendUdp(self, destination, port, payload='hello'): 2990 assert payload is not None, 'payload should not be none' 2991 cmd = 'udp send %s %d %s' % (destination, port, payload) 2992 return self.__executeCommand(cmd)[-1] == 'Done' 2993 2994 @API 2995 def send_udp(self, interface, destination, port, payload='12ABcd'): 2996 ''' payload hexstring 2997 ''' 2998 assert payload is not None, 'payload should not be none' 2999 assert interface == 0, "non-BR must send UDP to Thread interface" 3000 self.__udpOpen() 3001 time.sleep(0.5) 3002 cmd = 'udp send %s %s -x %s' % (destination, port, payload) 3003 return self.__executeCommand(cmd)[-1] == 'Done' 3004 3005 def __udpOpen(self): 3006 if not self.__isUdpOpened: 3007 cmd = 'udp open' 3008 self.__executeCommand(cmd) 3009 3010 # Bind to RLOC address and first dynamic port 3011 rlocAddr = self.getRloc() 3012 3013 cmd = 'udp bind %s 49152' % rlocAddr 3014 self.__executeCommand(cmd) 3015 3016 self.__isUdpOpened = True 3017 3018 @API 3019 def sendMACcmd(self, enh=False): 3020 cmd = 'mac send datarequest' 3021 return self.__executeCommand(cmd)[-1] == 'Done' 3022 3023 @API 3024 def sendMACdata(self, enh=False): 3025 cmd = 'mac send emptydata' 3026 return self.__executeCommand(cmd)[-1] == 'Done' 3027 3028 @API 3029 def setCSLsuspension(self, suspend): 3030 if suspend: 3031 self.__setPollPeriod(240 * 1000) 3032 else: 3033 self.__setPollPeriod(int(0.9 * self.ssedTimeout * 1000)) 3034 3035 @API 3036 def set_max_addrs_per_child(self, num): 3037 cmd = 'childip max %d' % int(num) 3038 self.__executeCommand(cmd) 3039 3040 @API 3041 def config_next_dua_status_rsp(self, mliid, status_code): 3042 if status_code >= 400: 3043 # map status_code to correct COAP response code 3044 a, b = divmod(status_code, 100) 3045 status_code = ((a & 0x7) << 5) + (b & 0x1f) 3046 3047 cmd = 'bbr mgmt dua %d' % status_code 3048 3049 if mliid is not None: 3050 mliid = mliid.replace(':', '') 3051 cmd += ' %s' % mliid 3052 3053 self.__executeCommand(cmd) 3054 3055 @API 3056 def getDUA(self): 3057 dua = self.getGUA('fd00:7d03') 3058 return dua 3059 3060 def __addDefaultDomainPrefix(self): 3061 self.configBorderRouter(P_dp=1, P_stable=1, P_on_mesh=1, P_default=1) 3062 3063 def __setDUA(self, sDua): 3064 """specify the DUA before Thread Starts.""" 3065 if isinstance(sDua, str): 3066 sDua = sDua.decode('utf8') 3067 iid = ipaddress.IPv6Address(sDua).packed[-8:] 3068 cmd = 'dua iid %s' % ''.join('%02x' % ord(b) for b in iid) 3069 return self.__executeCommand(cmd)[-1] == 'Done' 3070 3071 def __getMlIid(self): 3072 """get the Mesh Local IID.""" 3073 # getULA64() would return the full string representation 3074 mleid = ModuleHelper.GetFullIpv6Address(self.getULA64()).lower() 3075 mliid = mleid[-19:].replace(':', '') 3076 return mliid 3077 3078 def __setMlIid(self, sMlIid): 3079 """Set the Mesh Local IID before Thread Starts.""" 3080 assert ':' not in sMlIid 3081 cmd = 'mliid %s' % sMlIid 3082 self.__executeCommand(cmd) 3083 3084 @API 3085 def registerDUA(self, sAddr=''): 3086 self.__setDUA(sAddr) 3087 3088 @API 3089 def config_next_mlr_status_rsp(self, status_code): 3090 cmd = 'bbr mgmt mlr response %d' % status_code 3091 return self.__executeCommand(cmd)[-1] == 'Done' 3092 3093 @API 3094 def setMLRtimeout(self, iMsecs): 3095 """Setup BBR MLR Timeout to `iMsecs` seconds.""" 3096 self.setBbrDataset(MlrTimeout=iMsecs) 3097 3098 @API 3099 def stopListeningToAddr(self, sAddr): 3100 cmd = 'ipmaddr del ' + sAddr 3101 try: 3102 self.__executeCommand(cmd) 3103 except CommandError as ex: 3104 if ex.code == OT_ERROR_ALREADY: 3105 pass 3106 else: 3107 raise 3108 3109 return True 3110 3111 @API 3112 def registerMulticast(self, listAddr=('ff04::1234:777a:1',), timeout=MLR_TIMEOUT_MIN): 3113 """subscribe to the given ipv6 address (sAddr) in interface and send MLR.req OTA 3114 3115 Args: 3116 sAddr : str : Multicast address to be subscribed and notified OTA. 3117 """ 3118 for each_sAddr in listAddr: 3119 self._beforeRegisterMulticast(each_sAddr, timeout) 3120 3121 sAddr = ' '.join(listAddr) 3122 cmd = 'ipmaddr add ' + str(sAddr) 3123 3124 try: 3125 self.__executeCommand(cmd) 3126 except CommandError as ex: 3127 if ex.code == OT_ERROR_ALREADY: 3128 pass 3129 else: 3130 raise 3131 3132 @API 3133 def getMlrLogs(self): 3134 return self.externalCommissioner.getMlrLogs() 3135 3136 @API 3137 def migrateNetwork(self, channel=None, net_name=None): 3138 """migrate to another Thread Partition 'net_name' (could be None) 3139 on specified 'channel'. Make sure same Mesh Local IID and DUA 3140 after migration for DUA-TC-06/06b (DEV-1923) 3141 """ 3142 if channel is None: 3143 raise Exception('channel None') 3144 3145 if channel not in range(11, 27): 3146 raise Exception('channel %d not in [11, 26] Invalid' % channel) 3147 3148 print('new partition %s on channel %d' % (net_name, channel)) 3149 3150 mliid = self.__getMlIid() 3151 dua = self.getDUA() 3152 self.reset() 3153 deviceRole = self.deviceRole 3154 self.setDefaultValues() 3155 self.setChannel(channel) 3156 if net_name is not None: 3157 self.setNetworkName(net_name) 3158 self.__setMlIid(mliid) 3159 self.__setDUA(dua) 3160 return self.joinNetwork(deviceRole) 3161 3162 @API 3163 def setParentPrio(self, prio): 3164 cmd = 'parentpriority %u' % prio 3165 return self.__executeCommand(cmd)[-1] == 'Done' 3166 3167 @API 3168 def role_transition(self, role): 3169 cmd = 'mode %s' % OpenThreadTHCI._ROLE_MODE_DICT[role] 3170 return self.__executeCommand(cmd)[-1] == 'Done' 3171 3172 @API 3173 def setLeaderWeight(self, iWeight=72): 3174 self.__executeCommand('leaderweight %d' % iWeight) 3175 3176 @watched 3177 def isBorderRoutingEnabled(self): 3178 try: 3179 self.__executeCommand('br omrprefix') 3180 return True 3181 except CommandError: 3182 return False 3183 3184 def __detectZephyr(self): 3185 """Detect if the device is running Zephyr and adapt in that case""" 3186 3187 try: 3188 self._lineSepX = re.compile(r'\r\n|\r|\n') 3189 if self.__executeCommand(ZEPHYR_PREFIX + 'thread version')[0].isdigit(): 3190 self._cmdPrefix = ZEPHYR_PREFIX 3191 except CommandError: 3192 self._lineSepX = LINESEPX 3193 3194 def __discoverDeviceCapability(self): 3195 """Discover device capability according to version""" 3196 thver = self.__executeCommand('thread version')[0] 3197 if thver in ['1.3', '4'] and not self.IsBorderRouter: 3198 self.log("Setting capability of {}: (DevCapb.C_FTD13 | DevCapb.C_MTD13)".format(self)) 3199 self.DeviceCapability = OT13_CAPBS 3200 elif thver in ['1.3', '4'] and self.IsBorderRouter: 3201 self.log("Setting capability of {}: (DevCapb.C_BR13 | DevCapb.C_Host13)".format(self)) 3202 self.DeviceCapability = OT13BR_CAPBS 3203 elif thver in ['1.2', '3'] and not self.IsBorderRouter: 3204 self.log("Setting capability of {}: DevCapb.L_AIO | DevCapb.C_FFD | DevCapb.C_RFD".format(self)) 3205 self.DeviceCapability = OT12_CAPBS 3206 elif thver in ['1.2', '3'] and self.IsBorderRouter: 3207 self.log("Setting capability of BR {}: DevCapb.C_BBR | DevCapb.C_Host | DevCapb.C_Comm".format(self)) 3208 self.DeviceCapability = OT12BR_CAPBS 3209 elif thver in ['1.1', '2']: 3210 self.log("Setting capability of {}: DevCapb.V1_1".format(self)) 3211 self.DeviceCapability = OT11_CAPBS 3212 else: 3213 self.log("Capability not specified for {}".format(self)) 3214 self.DeviceCapability = DevCapb.NotSpecified 3215 assert False, thver 3216 3217 @staticmethod 3218 def __lstrip0x(s): 3219 """strip 0x at the beginning of a hex string if it exists 3220 3221 Args: 3222 s: hex string 3223 3224 Returns: 3225 hex string with leading 0x stripped 3226 """ 3227 if s.startswith('0x'): 3228 s = s[2:] 3229 3230 return s 3231 3232 @API 3233 def setCcmState(self, state=0): 3234 assert state in (0, 1), state 3235 self.__executeCommand("ccm {}".format("enable" if state == 1 else "disable")) 3236 3237 @API 3238 def setVrCheckSkip(self): 3239 self.__executeCommand("tvcheck disable") 3240 3241 3242class OpenThread(OpenThreadTHCI, IThci): 3243 3244 def _connect(self): 3245 print('My port is %s' % self) 3246 self.__lines = [] 3247 timeout = 10 3248 port_error = None 3249 3250 if self.port.startswith('COM'): 3251 for _ in range(int(timeout / 0.5)): 3252 time.sleep(0.5) 3253 try: 3254 self.__handle = serial.Serial(self.port, 115200, timeout=0, write_timeout=1) 3255 self.sleep(1) 3256 self.__handle.write('\r\n') 3257 self.sleep(0.1) 3258 self._is_net = False 3259 break 3260 except SerialException as port_error: 3261 self.log("{} port not ready, retrying to connect...".format(self.port)) 3262 else: 3263 raise SerialException("Could not open {} port: {}".format(self.port, port_error)) 3264 elif ':' in self.port: 3265 host, port = self.port.split(':') 3266 self.__handle = socket.create_connection((host, port)) 3267 self.__handle.setblocking(False) 3268 self._is_net = True 3269 else: 3270 raise Exception('Unknown port schema') 3271 3272 def _disconnect(self): 3273 if self.__handle: 3274 self.__handle.close() 3275 self.__handle = None 3276 3277 def _deviceBeforeReset(self): 3278 pass 3279 3280 def _deviceAfterReset(self): 3281 pass 3282 3283 def __restartAgentService(self): 3284 pass 3285 3286 def _beforeRegisterMulticast(self, sAddr, timeout): 3287 pass 3288 3289 def __socRead(self, size=512): 3290 if self._is_net: 3291 return self.__handle.recv(size) 3292 else: 3293 return self.__handle.read(size) 3294 3295 def __socWrite(self, data): 3296 if self._is_net: 3297 self.__handle.sendall(data) 3298 else: 3299 self.__handle.write(data) 3300 3301 def _cliReadLine(self): 3302 if len(self.__lines) > 1: 3303 return self.__lines.pop(0) 3304 3305 tail = '' 3306 if len(self.__lines) != 0: 3307 tail = self.__lines.pop() 3308 3309 try: 3310 tail += self.__socRead() 3311 except socket.error: 3312 logging.exception('%s: No new data', self) 3313 self.sleep(0.1) 3314 3315 self.__lines += self._lineSepX.split(tail) 3316 if len(self.__lines) > 1: 3317 return self.__lines.pop(0) 3318 3319 def _cliWriteLine(self, line): 3320 if self._cmdPrefix == ZEPHYR_PREFIX: 3321 if not line.startswith(self._cmdPrefix): 3322 line = self._cmdPrefix + line 3323 self.__socWrite(line + '\r') 3324 else: 3325 self.__socWrite(line + '\r\n') 3326 3327 def _onCommissionStart(self): 3328 pass 3329 3330 def _onCommissionStop(self): 3331 pass 3332