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