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