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