#!/usr/bin/env python
#
# Copyright (c) 2018, The OpenThread Authors.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the
#    names of its contributors may be used to endorse or promote products
#    derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
"""
>> Thread Host Controller Interface
>> Device : OpenThread_WpanCtl THCI
>> Class : OpenThread_WpanCtl
"""

import re
import time
import socket
import logging
from Queue import Queue

import serial
from IThci import IThci
from GRLLibs.UtilityModules.Test import Thread_Device_Role, Device_Data_Requirement, MacType
from GRLLibs.UtilityModules.enums import PlatformDiagnosticPacket_Direction, PlatformDiagnosticPacket_Type
from GRLLibs.UtilityModules.ModuleHelper import ModuleHelper
from GRLLibs.ThreadPacket.PlatformPackets import PlatformDiagnosticPacket, PlatformPackets
from GRLLibs.UtilityModules.Plugins.AES_CMAC import Thread_PBKDF2
"""regex: used to split lines"""
LINESEPX = re.compile(r'\r\n|\n')


class OpenThread_WpanCtl(IThci):
    LOWEST_POSSIBLE_PARTATION_ID = 0x1
    LINK_QUALITY_CHANGE_TIME = 100

    # Used for reference firmware version control for Test Harness.
    # This variable will be updated to match the OpenThread reference firmware
    # officially released.
    firmwarePrefix = 'OPENTHREAD/'

    def __init__(self, **kwargs):
        """initialize the serial port and default network parameters
        Args:
            **kwargs: Arbitrary keyword arguments
                      Includes 'EUI' and 'SerialPort'
        """
        try:
            self.UIStatusMsg = ''
            self.mac = kwargs.get('EUI')
            self.handle = None
            self.AutoDUTEnable = False
            self._is_net = False  # whether device is through ser2net
            self.logStatus = {'stop': 'stop', 'running': 'running', 'pauseReq': 'pauseReq', 'paused': 'paused'}
            self.logThreadStatus = self.logStatus['stop']
            # connection type 'ip' stands for SSH
            self.connectType = (kwargs.get('Param5')).strip().lower() if kwargs.get('Param5') is not None else 'usb'
            # comma separated CLI prompt, wpanctl cmd prefix, Wpan interface
            (self.prompt, self.wpan_cmd_prefix,
             self.wpan_interface) = (kwargs.get('Param8').strip().split(',')
                                     if kwargs.get('Param8') else ['#', 'wpanctl', 'wpan0'])
            self.wpan_cmd_prefix += ' '
            # comma separated setting commands
            self.precmd = (kwargs.get('Param9')).strip().split(',') if kwargs.get('Param9') else []
            if self.connectType == 'ip':
                self.dutIpv4 = kwargs.get('TelnetIP')
                self.dutPort = kwargs.get('TelnetPort')
                self.port = self.dutIpv4 + ':' + self.dutPort
                # username for SSH
                self.username = kwargs.get('Param6').strip() if kwargs.get('Param6') else None
                # password for SSH
                self.password = kwargs.get('Param7').strip() if kwargs.get('Param7') else None
            else:
                self.port = kwargs.get('SerialPort')
            self.intialize()
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('initialize() Error: ' + str(e))

    def __del__(self):
        """close the serial port connection"""
        try:
            self.closeConnection()
            self.deviceConnected = False
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('delete() Error: ' + str(e))

    def _expect(self, expected, times=50):
        """Find the `expected` line within `times` trials.

        Args:
            expected    str: the expected string
            times       int: number of trials
        """
        print('[%s] Expecting [%s]' % (self.port, expected))

        retry_times = 10
        while times > 0 and retry_times > 0:
            line = self._readline()
            print('[%s] Got line [%s]' % (self.port, line))

            if line == expected:
                print('[%s] Expected [%s]' % (self.port, expected))
                return

            if not line:
                retry_times -= 1
                time.sleep(0.1)

            times -= 1

        raise Exception('failed to find expected string[%s]' % expected)

    def _read(self, size=512):
        logging.info('%s: reading', self.port)
        if self._is_net:
            return self.handle.recv(size)
        else:
            return self.handle.read(size)

    def _write(self, data):
        logging.info('%s: writing', self.port)
        if self._is_net:
            self.handle.sendall(data)
        else:
            self.handle.write(data)

    def _readline(self):
        """Read exactly one line from the device

        Returns:
            None on no data
        """
        logging.info('%s: reading line', self.port)
        if len(self._lines) > 1:
            return self._lines.pop(0)

        tail = ''
        if len(self._lines):
            tail = self._lines.pop()

        try:
            tail += self._read()
        except socket.error:
            logging.exception('%s: No new data', self.port)
            time.sleep(0.1)

        self._lines += LINESEPX.split(tail)
        if len(self._lines) > 1:
            return self._lines.pop(0)

    def _sendline(self, line):
        """Send exactly one line to the device

        Args:
            line str: data send to device
        """
        logging.info('%s: sending line', self.port)
        # clear buffer
        self._lines = []
        try:
            self._read()
        except socket.error:
            logging.debug('%s: Nothing cleared', self.port)

        print('sending [%s]' % line)
        self._write(line + '\r\n')
        self._lines = []
        # wait for write to complete
        time.sleep(0.1)

    def __sendCommand(self, cmd):
        """send specific command to reference unit over serial port

        Args:
            cmd: OpenThread_WpanCtl command string

        Returns:
            Fail: Failed to send the command to reference unit and parse it
            Value: successfully retrieve the desired value from reference unit
            Error: some errors occur, indicates by the followed specific error number
        """
        logging.info('%s: sendCommand[%s]', self.port, cmd)
        if self.logThreadStatus == self.logStatus['running']:
            self.logThreadStatus = self.logStatus['pauseReq']
            while self.logThreadStatus not in (self.logStatus['paused'], self.logStatus['stop']):
                pass

        ssh_stdin = None
        ssh_stdout = None
        ssh_stderr = None
        try:
            # command retransmit times
            retry_times = 3
            while retry_times > 0:
                retry_times -= 1
                try:
                    if self._is_net:
                        ssh_stdin, ssh_stdout, ssh_stderr = self.handle.exec_command(cmd)
                    else:
                        self._sendline(cmd)
                        self._expect(cmd)
                except Exception as e:
                    logging.exception('%s: failed to send command[%s]: %s', self.port, cmd, str(e))
                    if retry_times == 0:
                        raise
                else:
                    break

            line = None
            response = []
            retry_times = 20
            stdout_lines = []
            stderr_lines = []
            if self._is_net:
                stdout_lines = ssh_stdout.readlines()
                stderr_lines = ssh_stderr.readlines()
                if stderr_lines:
                    for stderr_line in stderr_lines:
                        if re.search(r'Not\s+Found|failed\s+with\s+error', stderr_line.strip(), re.M | re.I):
                            print('Command failed: %s' % stderr_line)
                            return 'Fail'
                        print('Got line: %s' % stderr_line)
                        logging.info('%s: the read line is[%s]', self.port, stderr_line)
                        response.append(str(stderr_line.strip()))
                elif stdout_lines:
                    for stdout_line in stdout_lines:
                        logging.info('%s: the read line is[%s]', self.port, stdout_line)
                        if re.search(r'Not\s+Found|failed\s+with\s+error', stdout_line.strip(), re.M | re.I):
                            print('Command failed')
                            return 'Fail'
                        print('Got line: ' + stdout_line)
                        logging.info('%s: send command[%s] done!', self.port, cmd)
                        response.append(str(stdout_line.strip()))
                response.append(self.prompt)
                return response
            else:
                while retry_times > 0:
                    line = self._readline()
                    print('read line: %s' % line)
                    logging.info('%s: the read line is[%s]', self.port, line)
                    if line:
                        response.append(line)
                        if re.match(self.prompt, line):
                            break
                        elif re.search(r'Not\s+Found|failed\s+with\s+error', line, re.M | re.I):
                            print('Command failed')
                            return 'Fail'

                    retry_times -= 1
                    time.sleep(0.1)

                if retry_times == 0:
                    raise Exception('%s: failed to find end of response' % self.port)
                logging.info('%s: send command[%s] done!', self.port, cmd)
                return response
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('sendCommand() Error: ' + str(e))
            raise

    def __stripValue(self, value):
        """strip the special characters in the value

                Args:
                    value: value string

                Returns:
                    value string without special characters
        """
        if isinstance(value, str):
            if (value[0] == '"' and value[-1] == '"') or (value[0] == '[' and value[-1] == ']'):
                return value[1:-1]
        return value

    def __padIp6Addr(self, ip6Addr):
        segments = ip6Addr.split(':')
        empty = None
        for i, element in enumerate(segments):
            if empty is None and len(element) == 0:
                empty = i
            elif len(element) < 4:
                segments[i] = '0' * (4 - len(element)) + element

        if empty is not None:
            segments = segments[:empty] + ['0000'] * (8 - len(segments) + 1) + segments[empty + 1:]

        return ':'.join(segments)

    def __setDeviceMode(self, mode):
        """set thread device mode:

        Args:
           mode: thread device mode. 11=rdn, 9=rn
           r: rx-on-when-idle
           s: secure IEEE 802.15.4 data request
           d: full thread device
           n: full network data

        Returns:
           True: successful to set the device mode
           False: fail to set the device mode
        """
        print('call __setDeviceMode')

        try:
            cmd = self.wpan_cmd_prefix + 'setprop Thread:DeviceMode %d' % mode
            return self.__sendCommand(cmd)[0] != 'Fail'
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('setDeviceMode() Error: ' + str(e))

    def __setRouterUpgradeThreshold(self, iThreshold):
        """set router upgrade threshold

        Args:
            iThreshold: the number of active routers on the Thread network
                        partition below which a REED may decide to become a Router.

        Returns:
            True: successful to set the ROUTER_UPGRADE_THRESHOLD
            False: fail to set ROUTER_UPGRADE_THRESHOLD
        """
        print('call __setRouterUpgradeThreshold')
        try:
            cmd = self.wpan_cmd_prefix + 'setprop Thread:RouterUpgradeThreshold %s' % str(iThreshold)
            return self.__sendCommand(cmd)[0] != 'Fail'
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('setRouterUpgradeThreshold() Error: ' + str(e))

    def __setRouterDowngradeThreshold(self, iThreshold):
        """set router downgrade threshold

        Args:
            iThreshold: the number of active routers on the Thread network
                        partition above which an active router may decide to
                        become a child.

        Returns:
            True: successful to set the ROUTER_DOWNGRADE_THRESHOLD
            False: fail to set ROUTER_DOWNGRADE_THRESHOLD
        """
        print('call __setRouterDowngradeThreshold')
        try:
            cmd = self.wpan_cmd_prefix + 'setprop Thread:RouterDowngradeThreshold %s' % str(iThreshold)
            return self.__sendCommand(cmd)[0] != 'Fail'
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('setRouterDowngradeThreshold() Error: ' + str(e))

    def __setRouterSelectionJitter(self, iRouterJitter):
        """set ROUTER_SELECTION_JITTER parameter for REED to upgrade to Router

        Args:
            iRouterJitter: a random period prior to request Router ID for REED

        Returns:
            True: successful to set the ROUTER_SELECTION_JITTER
            False: fail to set ROUTER_SELECTION_JITTER
        """
        print('call _setRouterSelectionJitter')
        try:
            cmd = self.wpan_cmd_prefix + 'setprop Thread:RouterSelectionJitter %s' % str(iRouterJitter)
            return self.__sendCommand(cmd) != 'Fail'
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('setRouterSelectionJitter() Error: ' + str(e))

    def __setAddressfilterMode(self, mode):
        """set address filter mode

        Returns:
            True: successful to set address filter mode.
            False: fail to set address filter mode.
        """
        print('call setAddressFilterMode() %s' % mode)
        try:
            if mode in ('allowlist', 'denylist'):
                cmd = self.wpan_cmd_prefix + 'setprop MAC:' + mode.capitalize() + ':Enabled 1'
            elif mode == 'disable':
                if self._addressfilterMode != 'disable':
                    assert self._addressfilterMode in ('allowlist', 'denylist'), self._addressfilterMode
                    cmd = self.wpan_cmd_prefix + 'setprop MAC:' + self._addressfilterMode.capitalize() + ':Enabled 0'
                else:
                    return True
            else:
                assert False, 'unknown address filter mode: %s' % mode

            if self.__sendCommand(cmd)[0] != 'Fail':
                self._addressfilterMode = mode
                return True
            return False
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('__setAddressFilterMode() Error: ' + str(e))

    def __startOpenThreadWpan(self):
        """start OpenThreadWpan

        Returns:
            True: successful to start OpenThreadWpan up
            False: fail to start OpenThreadWpan
        """
        print('call startOpenThreadWpan')
        try:

            # restore allowlist/denylist address filter mode if rejoin after
            # reset
            if self.isPowerDown:
                if self._addressfilterMode == 'allowlist':
                    if self.__setAddressfilterMode('allowlist'):
                        for addr in self._addressfilterSet:
                            self.addAllowMAC(addr)
                elif self._addressfilterMode == 'denylist':
                    if self.__setAddressfilterMode('denylist'):
                        for addr in self._addressfilterSet:
                            self.addBlockedMAC(addr)
            time.sleep(1)
            if ModuleHelper.LeaderDutChannelFound:
                self.channel = ModuleHelper.Default_Channel
            nodeType = 'router'
            startType = 'join'
            if self.deviceRole == Thread_Device_Role.Leader:
                nodeType = 'router'
                startType = 'form'
            elif self.deviceRole == Thread_Device_Role.Router:
                nodeType = 'router'
            elif self.deviceRole == Thread_Device_Role.SED:
                nodeType = 'sed'
            elif self.deviceRole == Thread_Device_Role.EndDevice:
                nodeType = 'end'
            elif self.deviceRole == Thread_Device_Role.REED:
                nodeType = 'router'
            elif self.deviceRole == Thread_Device_Role.EndDevice_FED:
                nodeType = 'router'
            elif self.deviceRole == Thread_Device_Role.EndDevice_MED:
                nodeType = 'end'
            else:
                pass

            if self.deviceRole in [Thread_Device_Role.Leader, Thread_Device_Role.Router, Thread_Device_Role.REED]:
                self.__setRouterSelectionJitter(1)

            if startType == 'form':
                startCmd = self.wpan_cmd_prefix + '%s "%s" -c %s -T %s ' % (
                    startType,
                    self.networkName,
                    str(self.channel),
                    nodeType,
                )
            else:
                startCmd = self.wpan_cmd_prefix + '%s "%s" -p %s -c %s -T %s ' % (
                    startType,
                    self.networkName,
                    str(hex(self.panId)),
                    str(self.channel),
                    nodeType,
                )
            if self.__sendCommand(startCmd)[0] != 'Fail':
                if self.__isOpenThreadWpanRunning():
                    self.isPowerDown = False
                    if self.hasActiveDatasetToCommit:
                        if self.__sendCommand(self.wpan_cmd_prefix + 'setprop Dataset:Command SetActive')[0] == 'Fail':
                            raise Exception('failed to commit active dataset')
                        else:
                            self.hasActiveDatasetToCommit = False

                    return True
            else:
                return False
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('startOpenThreadWpan() Error: ' + str(e))

    def __stopOpenThreadWpan(self):
        """stop OpenThreadWpan

        Returns:
            True: successfully stop OpenThreadWpan
            False: failed to stop OpenThreadWpan
        """
        print('call stopOpenThreadWpan')
        try:
            if (self.__sendCommand(self.wpan_cmd_prefix + 'leave')[0] != 'Fail' and
                    self.__sendCommand(self.wpan_cmd_prefix + 'dataset erase')[0] != 'Fail'):
                return True
            else:
                return False
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('stopOpenThreadWpan() Error: ' + str(e))

    def __isOpenThreadWpanRunning(self):
        """check whether or not OpenThreadWpan is running

        Returns:
            True: OpenThreadWpan is running
            False: OpenThreadWpan is not running
        """
        print('call __isOpenThreadWpanRunning')
        if self.__stripValue(self.__sendCommand(self.wpan_cmd_prefix + 'getprop -v NCP:State')[0]) == 'associated':
            print('*****OpenThreadWpan is running')
            return True
        else:
            print('*****Wrong OpenThreadWpan state')
            return False

    # rloc16 might be hex string or integer, need to return actual allocated
    # router id
    def __convertRlocToRouterId(self, xRloc16):
        """mapping Rloc16 to router id

        Args:
            xRloc16: hex rloc16 short address

        Returns:
            actual router id allocated by leader
        """
        routerList = []
        routerList = self.__sendCommand(self.wpan_cmd_prefix + 'getprop -v Thread:RouterTable')
        print(routerList)
        print(xRloc16)

        for line in routerList:
            if re.match(r'\[|\]', line):
                continue
            if re.match(self.prompt, line, re.M | re.I):
                break
            router = []
            router = self.__stripValue(line).split(',')

            for item in router:
                if 'RouterId' in item:
                    routerid = item.split(':')[1]
                elif 'RLOC16' in line:
                    rloc16 = line.split(':')[1]
                else:
                    pass

            # process input rloc16
            if isinstance(xRloc16, str):
                rloc16 = '0x' + rloc16
                if rloc16 == xRloc16:
                    return routerid
            elif isinstance(xRloc16, int):
                if int(rloc16, 16) == xRloc16:
                    return routerid
            else:
                pass

        return None

    def __convertIp6PrefixStringToIp6Address(self, strIp6Prefix):
        """convert IPv6 prefix string to IPv6 dotted-quad format
           for example:
           2001000000000000 -> 2001:0000:0000:0000::

        Args:
            strIp6Prefix: IPv6 address string

        Returns:
            IPv6 address dotted-quad format
        """
        prefix1 = strIp6Prefix.rstrip('L')
        prefix2 = self.__lstrip0x(prefix1)
        hexPrefix = str(prefix2).ljust(16, '0')
        hexIter = iter(hexPrefix)
        finalMac = ':'.join(a + b + c + d for a, b, c, d in zip(hexIter, hexIter, hexIter, hexIter))
        prefix = str(finalMac)
        strIp6Prefix = prefix[:19]
        return strIp6Prefix + '::'

    def __convertLongToHex(self, iValue, fillZeros=None):
        """convert a long hex integer to string
           remove '0x' and 'L' return string

        Args:
            iValue: long integer in hex format
            fillZeros: pad string with zeros on the left to specified width

        Returns:
            string of this long integer without '0x' and 'L'
        """
        fmt = '%x'
        if fillZeros is not None:
            fmt = '%%0%dx' % fillZeros

        return fmt % iValue

    def __convertChannelMask(self, channelsArray):
        """convert channelsArray to bitmask format

        Args:
            channelsArray: channel array (i.e. [21, 22])

        Returns:
            bitmask format corresponding to a given channel array
        """
        maskSet = 0

        for eachChannel in channelsArray:
            mask = 1 << eachChannel
            maskSet = maskSet | mask

        return maskSet

    def __ChannelMaskListToStr(self, channelList):
        """convert a channel list to a string

        Args:
            channelList: channel list (i.e. [21, 22, 23])

        Returns:
            a comma separated channel string (i.e. '21, 22, 23')
        """
        chan_mask_range = ''
        if isinstance(channelList, list):
            if len(channelList):
                chan_mask_range = ','.join(str(chan) for chan in channelList)
            else:
                print('empty list')
        else:
            print('not a valid channel list: %s' % channelList)

        return chan_mask_range

    def __setChannelMask(self, channelMask):
        print('call _setChannelMask')
        try:
            cmd = self.wpan_cmd_prefix + 'setprop NCP:ChannelMask %s' % channelMask
            datasetCmd = self.wpan_cmd_prefix + 'setprop Dataset:ChannelMaskPage0 %s' % channelMask
            self.hasActiveDatasetToCommit = True
            return self.__sendCommand(cmd)[0] != 'Fail' and self.__sendCommand(datasetCmd)[0] != 'Fail'
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('setChannelMask() Error: ' + str(e))

    def __setSecurityPolicy(self, securityPolicySecs, securityPolicyFlags):
        print('call _setSecurityPolicy')
        try:
            cmd1 = self.wpan_cmd_prefix + 'setprop Dataset:SecPolicy:KeyRotation %s' % str(securityPolicySecs)
            if securityPolicyFlags == 'onrc':
                cmd2 = self.wpan_cmd_prefix + 'setprop Dataset:SecPolicy:Flags 0xf7'
            else:
                print('unknown policy flag :' + securityPolicyFlags)
                return False
            self.hasActiveDatasetToCommit = True
            return self.__sendCommand(cmd1) != 'Fail' and self.__sendCommand(cmd2) != 'Fail'
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('setSecurityPolicy() Error: ' + str(e))

    def __setKeySwitchGuardTime(self, iKeySwitchGuardTime):
        """ set the Key switch guard time

        Args:
            iKeySwitchGuardTime: key switch guard time

        Returns:
            True: successful to set key switch guard time
            False: fail to set key switch guard time
        """
        print('%s call setKeySwitchGuardTime' % self.port)
        print(iKeySwitchGuardTime)
        try:
            cmd = self.wpan_cmd_prefix + 'setprop Network:KeySwitchGuardTime %s' % str(iKeySwitchGuardTime)
            if self.__sendCommand(cmd)[0] != 'Fail':
                time.sleep(1)
                return True
            else:
                return False
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('setKeySwitchGuardTime() Error; ' + str(e))

    def __getCommissionerSessionId(self):
        """ get the commissioner session id allocated from Leader """
        print('%s call getCommissionerSessionId' % self.port)
        return self.__sendCommand(self.wpan_cmd_prefix + 'getprop -v Commissioner:SessionId')[0]

    def __getJoinerState(self):
        """ get joiner state """
        maxDuration = 150  # seconds
        t_end = time.time() + maxDuration
        while time.time() < t_end:
            joinerState = self.__stripValue(self.__sendCommand(self.wpan_cmd_prefix + 'getprop -v NCP:State')[0])
            if joinerState == 'offline:commissioned':
                return True
            elif joinerState == 'associating:credentials-needed':
                return False
            else:
                time.sleep(5)
                continue
        return False

    def _connect(self):
        if self.connectType == 'usb':
            print('My port is %s' % self.port)
            try:
                self.handle = serial.Serial(self.port, 115200, timeout=0.2)
            except Exception as e:
                ModuleHelper.WriteIntoDebugLogger('open serial error ' + str(e))

            try:
                attempts = 0
                user_prompted = False
                pwd_prompted = False
                while attempts < 10 or pwd_prompted:
                    time.sleep(0.5)
                    attempts = attempts + 1
                    print('attempts...%d' % attempts)

                    input_data = self.handle.read(self.handle.inWaiting())

                    if not input_data:
                        if not user_prompted:
                            self.handle.write('\n')
                        time.sleep(0.5)
                        continue

                    if 'login' in input_data:
                        self.handle.write(self.username + '\n')
                        time.sleep(0.3)
                        print('user prompted')
                        user_prompted = True

                    elif 'Password' in input_data:
                        print('password prompted')
                        time.sleep(0.3)
                        self.handle.write(self.password + '\n')
                        pwd_prompted = True

                    elif self.prompt in input_data:
                        print('login success (serial)')
                        time.sleep(0.3)
                        self.deviceConnected = True
                        for precmd in self.precmd:
                            self.handle.write(precmd + '\n')
                            time.sleep(0.3)
                        time.sleep(1)
                        break
                if not self.deviceConnected:
                    raise Exception('login fail')
                else:
                    self._is_net = False
            except Exception as e:
                ModuleHelper.WriteIntoDebugLogger('connect to serial Error: ' + str(e))

        elif self.connectType == 'ip':
            print('My IP: %s Port: %s' % (self.dutIpv4, self.dutPort))
            try:
                import paramiko

                if not self.password:
                    transport = paramiko.Transport((self.dutIpv4, int(self.dutPort)))
                    transport.start_client()
                    transport.auth_none(self.username)
                    self.handle = paramiko.SSHClient()
                    self.handle.set_missing_host_key_policy(paramiko.AutoAddPolicy())
                    self.handle._transport = transport
                else:
                    self.handle = paramiko.SSHClient()
                    self.handle.set_missing_host_key_policy(paramiko.AutoAddPolicy())
                    self.handle.connect(self.dutIpv4,
                                        port=int(self.dutPort),
                                        username=self.username,
                                        password=self.password)
                print('login success (ssh)')
                self.deviceConnected = True
                for precmd in self.precmd:
                    self.handle.exec_command(precmd + '\n')
                    time.sleep(0.5)
                self._is_net = True

            except Exception as e:
                ModuleHelper.WriteIntoDebugLogger('connect to ssh Error: ' + str(e))
        else:
            raise Exception('Unknown port schema')

    def closeConnection(self):
        """close current serial port connection"""
        print('%s call closeConnection' % self.port)
        try:
            if self.handle:
                self.handle.close()
                self.handle = None
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('closeConnection() Error: ' + str(e))

    def intialize(self):
        """initialize the serial port with baudrate, timeout parameters"""
        print('%s call intialize' % self.port)
        try:
            # init serial port
            self.deviceConnected = False
            self._connect()

            if self.deviceConnected:
                self.UIStatusMsg = self.getVersionNumber()
                if self.firmwarePrefix not in self.UIStatusMsg:
                    self.deviceConnected = False
                    self.UIStatusMsg = ('Firmware Not Matching Expecting ' + self.firmwarePrefix + ' Now is ' +
                                        self.UIStatusMsg)
                    raise Exception('Err: OpenThread device Firmware not matching..')
                self.__sendCommand(self.wpan_cmd_prefix + 'leave')
                self.__sendCommand(self.wpan_cmd_prefix + 'dataset erase')
            else:
                raise Exception('Err: Device not connected ..')

        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('intialize() Error: ' + str(e))

    def setNetworkName(self, networkName='GRL'):
        """set Thread Network name

        Args:
            networkName: the networkname string to be set

        Returns:
            True: successful to set the Thread Networkname
            False: fail to set the Thread Networkname
        """
        print('%s call setNetworkName' % self.port)
        assert '"' not in networkName

        try:
            cmd = self.wpan_cmd_prefix + 'setprop -s Network:Name "%s"' % networkName
            datasetCmd = self.wpan_cmd_prefix + 'setprop Dataset:NetworkName "%s"' % networkName
            self.hasActiveDatasetToCommit = True
            return self.__sendCommand(cmd)[0] != 'Fail' and self.__sendCommand(datasetCmd)[0] != 'Fail'
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('setNetworkName() Error: ' + str(e))

    def setChannel(self, channel=15):
        """set channel of Thread device operates on.

        Args:
            channel:
                    (0  - 10: Reserved)
                    (11 - 26: 2.4GHz channels)
                    (27 - 65535: Reserved)

        Returns:
            True: successful to set the channel
            False: fail to set the channel
        """
        print('%s call setChannel' % self.port)

        try:
            cmd = self.wpan_cmd_prefix + 'setprop NCP:Channel %s' % channel
            datasetCmd = self.wpan_cmd_prefix + 'setprop Dataset:Channel %s' % channel
            self.hasActiveDatasetToCommit = True
            return self.__sendCommand(cmd)[0] != 'Fail' and self.__sendCommand(datasetCmd)[0] != 'Fail'
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('setChannel() Error: ' + str(e))

    def getChannel(self):
        """get current channel"""
        print('%s call getChannel' % self.port)
        return self.__sendCommand(self.wpan_cmd_prefix + 'getprop -v NCP:Channel')[0]

    def setMAC(self, xEUI):
        """set the extended address of Thread device

        Args:
            xEUI: extended address in hex format

        Returns:
            True: successful to set the extended address
            False: fail to set the extended address
        """
        print('%s call setMAC' % self.port)

        address64 = ''
        try:
            if not isinstance(xEUI, str):
                address64 = self.__convertLongToHex(xEUI, 16)
            else:
                address64 = xEUI

            cmd = self.wpan_cmd_prefix + 'setprop NCP:MACAddress %s' % address64

            if self.__sendCommand(cmd)[0] != 'Fail':
                self.mac = address64
                return True
            else:
                return False
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('setMAC() Error: ' + str(e))

    def getMAC(self, bType=MacType.RandomMac):
        """get one specific type of MAC address
           currently OpenThreadWpan only supports Random MAC address

        Args:
            bType: indicate which kind of MAC address is required

        Returns:
            specific type of MAC address
        """
        print('%s call getMAC' % self.port)

        # if power down happens, return extended address assigned previously
        if self.isPowerDown:
            macAddr64 = self.mac
        else:
            if bType == MacType.FactoryMac:
                macAddr64 = self.__stripValue(
                    self.__sendCommand(self.wpan_cmd_prefix + 'getprop -v NCP:HardwareAddress')[0])
            elif bType == MacType.HashMac:
                macAddr64 = self.__stripValue(
                    self.__sendCommand(self.wpan_cmd_prefix + 'getprop -v NCP:MACAddress')[0])
            else:
                macAddr64 = self.__stripValue(
                    self.__sendCommand(self.wpan_cmd_prefix + 'getprop -v NCP:ExtendedAddress')[0])

        return int(macAddr64, 16)

    def getLL64(self):
        """get link local unicast IPv6 address"""
        print('%s call getLL64' % self.port)
        return self.__stripValue(self.__sendCommand(self.wpan_cmd_prefix + 'getprop -v IPv6:LinkLocalAddress')[0])

    def getMLEID(self):
        """get mesh local endpoint identifier address"""
        print('%s call getMLEID' % self.port)
        return self.__stripValue(self.__sendCommand(self.wpan_cmd_prefix + 'getprop -v IPv6:MeshLocalAddress')[0])

    def getRloc16(self):
        """get rloc16 short address"""
        print('%s call getRloc16' % self.port)
        rloc16 = self.__sendCommand(self.wpan_cmd_prefix + 'getprop -v Thread:RLOC16')[0]
        return int(rloc16, 16)

    def getRloc(self):
        """get router locator unicast IPv6 address"""
        print('%s call getRloc' % self.port)
        prefix = self.__stripValue(self.__sendCommand(self.wpan_cmd_prefix + 'getprop -v IPv6:MeshLocalPrefix')[0])
        mlprefix = prefix.split('/')[0]
        rloc16 = self.__lstrip0x(self.__sendCommand(self.wpan_cmd_prefix + 'getprop -v Thread:RLOC16')[0])

        print('prefix: %s' % prefix)
        print('mlprefix: %s ' % mlprefix)
        print('rloc16: %s' % rloc16)

        rloc = self.__padIp6Addr(mlprefix + '00ff:fe00:' + rloc16)
        print('rloc: %s' % rloc)
        return rloc

    def getGlobal(self):
        """get global unicast IPv6 address set
           if configuring multiple entries
        """
        print('%s call getGlobal' % self.port)
        globalAddrs = []

        mleid = self.__stripValue(self.__sendCommand(self.wpan_cmd_prefix + 'getprop -v IPv6:MeshLocalAddress')[0])

        mleid = ModuleHelper.GetFullIpv6Address(mleid).lower()

        addrs = self.__sendCommand(self.wpan_cmd_prefix + 'getprop -v IPv6:AllAddresses')

        # find rloc address firstly as a reference for current mesh local prefix as for some TCs,
        # mesh local prefix may be updated through pending dataset management.
        for ip6AddrItem in addrs:
            if re.match(r'\[|\]', ip6AddrItem):
                continue
            if re.match(self.prompt, ip6AddrItem, re.M | re.I):
                break
            ip6AddrItem = ip6AddrItem.strip()
            ip6Addr = self.__stripValue(ip6AddrItem).split(' ')[0]

            fullIp = ModuleHelper.GetFullIpv6Address(ip6Addr).lower()

            print('fullip %s' % fullIp)

            if fullIp.startswith('fe80'):
                continue

            if fullIp.startswith(mleid[0:19]):
                continue

            print('global')
            globalAddrs.append(fullIp)

        return globalAddrs

    def setNetworkKey(self, key):
        """set Thread network key

        Args:
            key: Thread network key used in secure the MLE/802.15.4 packet

        Returns:
            True: successful to set the Thread network key
            False: fail to set the Thread network key
        """
        networkKey = ''
        print('%s call setNetworkKey' % self.port)

        try:
            if not isinstance(key, str):
                networkKey = self.__convertLongToHex(key, 32)
                cmd = self.wpan_cmd_prefix + 'setprop Network:Key %s' % networkKey
                datasetCmd = self.wpan_cmd_prefix + 'setprop Dataset:NetworkKey %s' % networkKey
            else:
                networkKey = key
                cmd = self.wpan_cmd_prefix + 'setprop Network:Key %s' % networkKey
                datasetCmd = self.wpan_cmd_prefix + 'setprop Dataset:NetworkKey %s' % networkKey

            self.networkKey = networkKey
            self.hasActiveDatasetToCommit = True
            return self.__sendCommand(cmd)[0] != 'Fail' and self.__sendCommand(datasetCmd)[0] != 'Fail'
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('setNetworkkey() Error: ' + str(e))

    def addBlockedMAC(self, xEUI):
        """add a given extended address to the denylist entry

        Args:
            xEUI: extended address in hex format

        Returns:
            True: successful to add a given extended address to the denylist entry
            False: fail to add a given extended address to the denylist entry
        """
        print('%s call addBlockedMAC' % self.port)
        print(xEUI)
        if isinstance(xEUI, str):
            macAddr = xEUI
        else:
            macAddr = self.__convertLongToHex(xEUI)

        try:
            # if blocked device is itself
            if macAddr == self.mac:
                print('block device itself')
                return True

            if self._addressfilterMode != 'denylist':
                self.__setAddressfilterMode('denylist')

            cmd = self.wpan_cmd_prefix + 'insert MAC:Denylist:Entries %s' % macAddr
            ret = self.__sendCommand(cmd)[0] != 'Fail'

            self._addressfilterSet.add(macAddr)
            print('current denylist entries:')
            for addr in self._addressfilterSet:
                print(addr)

            return ret
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('addBlockedMAC() Error: ' + str(e))

    def addAllowMAC(self, xEUI):
        """add a given extended address to the allowlist addressfilter

        Args:
            xEUI: a given extended address in hex format

        Returns:
            True: successful to add a given extended address to the allowlist entry
            False: fail to add a given extended address to the allowlist entry
        """
        print('%s call addAllowMAC' % self.port)
        print(xEUI)
        if isinstance(xEUI, str):
            macAddr = xEUI
        else:
            macAddr = self.__convertLongToHex(xEUI)

        try:
            if self._addressfilterMode != 'allowlist':
                self.__setAddressfilterMode('allowlist')

            cmd = self.wpan_cmd_prefix + 'insert MAC:Allowlist:Entries %s' % macAddr
            ret = self.__sendCommand(cmd)[0] != 'Fail'

            self._addressfilterSet.add(macAddr)
            print('current allowlist entries:')
            for addr in self._addressfilterSet:
                print(addr)
            return ret

        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('addAllowMAC() Error: ' + str(e))

    def clearBlockList(self):
        """clear all entries in denylist table

        Returns:
            True: successful to clear the denylist
            False: fail to clear the denylist
        """
        print('%s call clearBlockList' % self.port)

        # remove all entries in denylist
        try:
            print('clearing denylist entries:')
            for addr in self._addressfilterSet:
                print(addr)

            # disable denylist
            if self.__setAddressfilterMode('disable'):
                # clear ops
                for addr in self._addressfilterSet:
                    cmd = self.wpan_cmd_prefix + 'remove MAC:Denylist:Entries ' + addr
                    self.__sendCommand(cmd)

                self._addressfilterSet.clear()
                return True

            return False
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('clearBlockList() Error: ' + str(e))

    def clearAllowList(self):
        """clear all entries in allowlist table

        Returns:
            True: successful to clear the allowlist
            False: fail to clear the allowlist
        """
        print('%s call clearAllowList' % self.port)

        # remove all entries in allowlist
        try:
            print('clearing allowlist entries:')
            for addr in self._addressfilterSet:
                print(addr)

            # disable allowlist
            if self.__setAddressfilterMode('disable'):
                # clear ops
                for addr in self._addressfilterSet:
                    cmd = self.wpan_cmd_prefix + 'remove MAC:Allowlist:Entries ' + addr
                    self.__sendCommand(cmd)

                self._addressfilterSet.clear()
                return True

            return False
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('clearAllowList() Error: ' + str(e))

    def getDeviceRole(self):
        """get current device role in Thread Network"""
        print('%s call getDeviceRole' % self.port)
        return self.__stripValue(self.__sendCommand(self.wpan_cmd_prefix + 'getprop -v Network:NodeType')[0])

    def joinNetwork(self, eRoleId):
        """make device ready to join the Thread Network with a given role

        Args:
            eRoleId: a given device role id

        Returns:
            True: ready to set Thread Network parameter for joining desired Network
        """
        print('%s call joinNetwork' % self.port)
        print(eRoleId)

        self.deviceRole = eRoleId
        mode = 15
        try:
            if ModuleHelper.LeaderDutChannelFound:
                self.channel = ModuleHelper.Default_Channel

            # FIXME: when Harness call setNetworkDataRequirement()?
            # only sleep end device requires stable networkdata now
            if eRoleId == Thread_Device_Role.Leader:
                print('join as leader')
                # rdn
                mode = 15
                if self.AutoDUTEnable is False:
                    # set ROUTER_DOWNGRADE_THRESHOLD
                    self.__setRouterDowngradeThreshold(33)
            elif eRoleId == Thread_Device_Role.Router:
                print('join as router')
                # rdn
                mode = 15
                if self.AutoDUTEnable is False:
                    # set ROUTER_DOWNGRADE_THRESHOLD
                    self.__setRouterDowngradeThreshold(33)
            elif eRoleId == Thread_Device_Role.SED:
                print('join as sleepy end device')
                # s
                mode = 4
                self.__setPollPeriod(self.__sedPollPeriod)
            elif eRoleId == Thread_Device_Role.EndDevice:
                print('join as end device')
                # rn
                mode = 13
            elif eRoleId == Thread_Device_Role.REED:
                print('join as REED')
                # rdn
                mode = 15
                # set ROUTER_UPGRADE_THRESHOLD
                self.__setRouterUpgradeThreshold(0)
            elif eRoleId == Thread_Device_Role.EndDevice_FED:
                # always remain an ED, never request to be a router
                print('join as FED')
                # rdn
                mode = 15
                # set ROUTER_UPGRADE_THRESHOLD
                self.__setRouterUpgradeThreshold(0)
            elif eRoleId == Thread_Device_Role.EndDevice_MED:
                print('join as MED')
                # rn
                mode = 13
            else:
                pass

            # set Thread device mode with a given role
            self.__setDeviceMode(mode)
            time.sleep(0.1)
            # start OpenThreadWpan
            self.__startOpenThreadWpan()
            time.sleep(3)
            return True
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('joinNetwork() Error: ' + str(e))

    def getNetworkFragmentID(self):
        """get current partition id of Thread Network Partition from LeaderData

        Returns:
            The Thread network Partition Id
        """
        print('%s call getNetworkFragmentID' % self.port)
        if not self.____isOpenThreadWpanRunning():
            print('OpenThreadWpan is not running')
            return None

        return self.__sendCommand(self.wpan_cmd_prefix + 'getprop -v Network:PartitionId')[0]

    def getParentAddress(self):
        """get Thread device's parent extended address and rloc16 short address

        Returns:
            The extended address of parent in hex format
        """
        print('%s call getParentAddress' % self.port)
        parentInfo = []
        parentInfo = self.__stripValue(self.__sendCommand(self.wpan_cmd_prefix +
                                                          'getprop -v Thread:Parent')).split(' ')

        return parentInfo[0]

    def powerDown(self):
        """power down the OpenThreadWpan"""
        print('%s call powerDown' % self.port)
        if self.__sendCommand(self.wpan_cmd_prefix + 'setprop Daemon:AutoAssociateAfterReset false')[0] != 'Fail':
            time.sleep(0.5)
            if self.__sendCommand(self.wpan_cmd_prefix + 'reset')[0] != 'Fail':
                self.isPowerDown = True
                return True
            else:
                return False
        else:
            return False

    def powerUp(self):
        """power up the Thread device"""
        print('%s call powerUp' % self.port)
        if not self.handle:
            self._connect()

        self.isPowerDown = False
        if self.__sendCommand(self.wpan_cmd_prefix + 'attach')[0] != 'Fail':
            time.sleep(3)
        else:
            return False

        if self.__sendCommand(self.wpan_cmd_prefix + 'setprop Daemon:AutoAssociateAfterReset true')[0] == 'Fail':
            return False

        if self.__stripValue(self.__sendCommand(self.wpan_cmd_prefix + 'getprop -v NCP:State')[0]) != 'associated':
            print('powerUp failed')
            return False
        else:
            return True

    def reboot(self):
        """reset and rejoin to Thread Network without any timeout

        Returns:
            True: successful to reset and rejoin the Thread Network
            False: fail to reset and rejoin the Thread Network
        """
        print('%s call reboot' % self.port)
        try:
            self._sendline(self.wpan_cmd_prefix + 'reset')
            self.isPowerDown = True

            if self.__sendCommand(self.wpan_cmd_prefix + 'getprop -v NCP:State')[0] != 'associated':
                print('[FAIL] reboot')
                return False
            else:
                return True
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('reboot() Error: ' + str(e))

    def ping(self, destination, length=20):
        """ send ICMPv6 echo request with a given length to a unicast destination
            address

        Args:
            destination: the unicast destination address of ICMPv6 echo request
            length: the size of ICMPv6 echo request payload
        """
        print('%s call ping' % self.port)
        print('destination: %s' % destination)
        try:
            cmd = 'ping %s -c 1 -s %s -I %s' % (destination, str(length), self.wpan_interface)
            if self._is_net:
                self.handle.exec_command(cmd)
            else:
                self._sendline(cmd)
                self._expect(cmd)
            # wait echo reply
            time.sleep(1)
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('ping() Error: ' + str(e))

    def multicast_Ping(self, destination, length=20):
        """send ICMPv6 echo request with a given length to a multicast destination
           address

        Args:
            destination: the multicast destination address of ICMPv6 echo request
            length: the size of ICMPv6 echo request payload
        """
        print('%s call multicast_Ping' % self.port)
        print('destination: %s' % destination)
        try:
            cmd = 'ping %s -c 1 -s %s -I %s' % (destination, str(length), self.wpan_interface)
            if self._is_net:
                self.handle.exec_command(cmd)
            else:
                self._sendline(cmd)
                self._expect(cmd)
            # wait echo reply
            time.sleep(1)
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('multicast_ping() Error: ' + str(e))

    def getVersionNumber(self):
        """get OpenThreadWpan stack firmware version number"""
        print('%s call getVersionNumber' % self.port)
        versionStr = self.__sendCommand(self.wpan_cmd_prefix + 'getprop -v NCP:Version')[0]

        return self.__stripValue(versionStr)

    def setPANID(self, xPAN):
        """set Thread Network PAN ID

        Args:
            xPAN: a given PAN ID in hex format

        Returns:
            True: successful to set the Thread Network PAN ID
            False: fail to set the Thread Network PAN ID
        """
        print('%s call setPANID' % self.port)
        print(xPAN)
        panid = ''
        try:
            if not isinstance(xPAN, str):
                panid = str(hex(xPAN))
                print(panid)

            cmd = self.wpan_cmd_prefix + 'setprop -s Network:PANID %s' % panid
            datasetCmd = self.wpan_cmd_prefix + 'setprop Dataset:PanId %s' % panid
            self.hasActiveDatasetToCommit = True
            return self.__sendCommand(cmd)[0] != 'Fail' and self.__sendCommand(datasetCmd)[0] != 'Fail'
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('setPANID() Error: ' + str(e))

    def reset(self):
        """factory reset"""
        print('%s call reset' % self.port)
        try:
            if self._is_net:
                self.__sendCommand(self.wpan_cmd_prefix + 'leave')
            else:
                self._sendline(self.wpan_cmd_prefix + 'leave')

            self.__sendCommand(self.wpan_cmd_prefix + 'dataset erase')
            time.sleep(2)
            if not self._is_net:
                self._read()
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('reset() Error: ' + str(e))

    def removeRouter(self, xRouterId):
        """kick router with a given router id from the Thread Network

        Args:
            xRouterId: a given router id in hex format

        Returns:
            True: successful to remove the router from the Thread Network
            False: fail to remove the router from the Thread Network
        """
        print('%s call removeRouter' % self.port)
        print(xRouterId)
        routerId = ''
        routerId = self.__convertRlocToRouterId(xRouterId)
        print(routerId)

        if routerId is None:
            print('no matched xRouterId')
            return False

        try:
            cmd = 'releaserouterid %s' % routerId
            return self.__sendCommand(cmd)[0] != 'Fail'
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('removeRouter() Error: ' + str(e))

    def setDefaultValues(self):
        """set default mandatory Thread Network parameter value"""
        print('%s call setDefaultValues' % self.port)

        # initialize variables
        self.networkName = ModuleHelper.Default_NwkName
        assert '"' not in self.networkName
        self.networkKey = ModuleHelper.Default_NwkKey
        self.channel = ModuleHelper.Default_Channel
        self.channelMask = '0x7fff800'  # (0xffff << 11)
        self.panId = ModuleHelper.Default_PanId
        self.xpanId = ModuleHelper.Default_XpanId
        self.meshLocalPrefix = ModuleHelper.Default_MLPrefix
        # OT only accept hex format PSKc for now
        self.pskc = '00000000000000000000000000000000'
        self.securityPolicySecs = ModuleHelper.Default_SecurityPolicy
        self.securityPolicyFlags = 'onrc'
        self.activetimestamp = ModuleHelper.Default_ActiveTimestamp
        # self.sedPollingRate = ModuleHelper.Default_Harness_SED_Polling_Rate
        self.__sedPollPeriod = 3 * 1000  # in milliseconds
        self.deviceRole = None
        self.provisioningUrl = ''
        self.hasActiveDatasetToCommit = False
        self.logThread = Queue()
        self.logThreadStatus = self.logStatus['stop']
        # indicate Thread device requests full or stable network data
        self.networkDataRequirement = ''
        # indicate if Thread device experiences a power down event
        self.isPowerDown = False
        # indicate AddressFilter mode ['disable', 'allowlist', 'denylist']
        self._addressfilterMode = 'disable'
        self._addressfilterSet = set()  # cache filter entries
        # indicate if Thread device is an active commissioner
        self.isActiveCommissioner = False
        self._lines = None  # buffered lines read from device

        # initialize device configuration
        try:
            self.setMAC(self.mac)
            self.__setChannelMask(self.channelMask)
            self.__setSecurityPolicy(self.securityPolicySecs, self.securityPolicyFlags)
            self.setChannel(self.channel)
            self.setPANID(self.panId)
            self.setXpanId(self.xpanId)
            self.setNetworkName(self.networkName)
            self.setNetworkKey(self.networkKey)
            self.setMLPrefix(self.meshLocalPrefix)
            self.setPSKc(self.pskc)
            self.setActiveTimestamp(self.activetimestamp)
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('setDefaultValue() Error: ' + str(e))

    def getDeviceConncetionStatus(self):
        """check if serial port connection is ready or not"""
        print('%s call getDeviceConnectionStatus' % self.port)
        return self.deviceConnected

    def setPollingRate(self, iPollingRate):
        """set data polling rate for sleepy end device

        Args:
            iPollingRate: data poll period of sleepy end device (in seconds)

        Returns:
            True: successful to set the data polling rate for sleepy end device
            False: fail to set the data polling rate for sleepy end device
        """
        print('%s call setPollingRate' % self.port)

        iPollingRate = int(iPollingRate * 1000)
        print(iPollingRate)

        if self.__sedPollPeriod != iPollingRate:
            self.__sedPollPeriod = iPollingRate

            # apply immediately
            if self.__isOpenThreadWpanRunning():
                return self.__setPollPeriod(self.__sedPollPeriod)

        return True

    def __setPollPeriod(self, iPollPeriod):
        """set data poll period for sleepy end device

        Args:
            iPollPeriod: data poll period of sleepy end device (in milliseconds)

        Returns:
            True: successful to set the data poll period for sleepy end device
            False: fail to set the data poll period for sleepy end device
        """
        try:
            cmd = self.wpan_cmd_prefix + 'setprop NCP:SleepyPollInterval %s' % str(iPollPeriod)
            print(cmd)
            return self.__sendCommand(cmd)[0] != 'Fail'
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('__setPollingRate() Error: ' + str(e))

    def setLinkQuality(self, EUIadr, LinkQuality):
        """set custom LinkQualityIn for all receiving messages from the specified EUIadr

        Args:
            EUIadr: a given extended address
            LinkQuality: a given custom link quality
                         link quality/link margin mapping table
                         3: 21 - 255 (dB)
                         2: 11 - 20 (dB)
                         1: 3 - 9 (dB)
                         0: 0 - 2 (dB)

        Returns:
            True: successful to set the link quality
            False: fail to set the link quality

        @todo: required if as reference device
        """

    def setOutBoundLinkQuality(self, LinkQuality):
        """set custom LinkQualityIn for all receiving messages from the any address

        Args:
            LinkQuality: a given custom link quality
                         link quality/link margin mapping table
                         3: 21 - 255 (dB)
                         2: 11 - 20 (dB)
                         1: 3 - 9 (dB)
                         0: 0 - 2 (dB)

        Returns:
            True: successful to set the link quality
            False: fail to set the link quality

        @todo: required if as reference device
        """

    def removeRouterPrefix(self, prefixEntry):
        """remove the configured prefix on a border router

        Args:
            prefixEntry: a on-mesh prefix entry

        Returns:
            True: successful to remove the prefix entry from border router
            False: fail to remove the prefix entry from border router

        @todo: required if as reference device
        """

    def resetAndRejoin(self, timeout):
        """reset and join back Thread Network with a given timeout delay

        Args:
            timeout: a timeout interval before rejoin Thread Network

        Returns:
            True: successful to reset and rejoin Thread Network
            False: fail to reset and rejoin the Thread Network
        """
        print('%s call resetAndRejoin' % self.port)
        print(timeout)
        try:
            if self.__sendCommand(self.wpan_cmd_prefix + 'setprop Daemon:AutoAssociateAfterReset false')[0] != 'Fail':
                time.sleep(0.5)
                if self.__sendCommand(self.wpan_cmd_prefix + 'reset')[0] != 'Fail':
                    self.isPowerDown = True
                else:
                    return False
            else:
                return False
            time.sleep(timeout)

            if self.deviceRole == Thread_Device_Role.SED:
                self.__setPollPeriod(self.__sedPollPeriod)

            if self.__sendCommand(self.wpan_cmd_prefix + 'attach')[0] != 'Fail':
                time.sleep(3)
            else:
                return False

            if self.__sendCommand(self.wpan_cmd_prefix + 'setprop Daemon:AutoAssociateAfterReset true')[0] == 'Fail':
                return False

            if self.__stripValue(self.__sendCommand(self.wpan_cmd_prefix + 'getprop -v NCP:State')[0]) != 'associated':
                print('[FAIL] reset and rejoin')
                return False
            return True
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('resetAndRejoin() Error: ' + str(e))

    def configBorderRouter(self,
                           P_Prefix,
                           P_stable=1,
                           P_default=1,
                           P_slaac_preferred=0,
                           P_Dhcp=0,
                           P_preference=0,
                           P_on_mesh=1,
                           P_nd_dns=0):
        """configure the border router with a given prefix entry parameters

        Args:
            P_Prefix: IPv6 prefix that is available on the Thread Network
            P_stable: true if the default router is expected to be stable network data
            P_default: true if border router offers the default route for P_Prefix
            P_slaac_preferred: true if allowing auto-configure address using P_Prefix
            P_Dhcp: is true if border router is a DHCPv6 Agent
            P_preference: is two-bit signed integer indicating router preference
            P_on_mesh: is true if P_Prefix is considered to be on-mesh
            P_nd_dns: is true if border router is able to supply DNS information obtained via ND

        Returns:
            True: successful to configure the border router with a given prefix entry
            False: fail to configure the border router with a given prefix entry
        """
        print('%s call configBorderRouter' % self.port)
        prefix = self.__convertIp6PrefixStringToIp6Address(str(P_Prefix))
        print(prefix)
        try:
            parameter = ''

            if P_slaac_preferred == 1:
                parameter += ' -a -f'

            if P_stable == 1:
                parameter += ' -s'

            if P_default == 1:
                parameter += ' -r'

            if P_Dhcp == 1:
                parameter += ' -d'

            if P_on_mesh == 1:
                parameter += ' -o'

            cmd = self.wpan_cmd_prefix + 'add-prefix %s %s -P %d' % (prefix, parameter, P_preference)
            print(parameter)
            print(cmd)
            if self.__sendCommand(cmd)[0] != 'Fail':
                return True
            else:
                return False
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('configBorderRouter() Error: ' + str(e))

    def setNetworkIDTimeout(self, iNwkIDTimeOut):
        """set networkid timeout for OpenThreadWpan

        Args:
            iNwkIDTimeOut: a given NETWORK_ID_TIMEOUT

        Returns:
            True: successful to set NETWORK_ID_TIMEOUT
            False: fail to set NETWORK_ID_TIMEOUT

        @todo: required if as reference device
        """

    def setKeepAliveTimeOut(self, iTimeOut):
        """set keep alive timeout for device
           has been deprecated and also set SED polling rate

        Args:
            iTimeOut: data poll period for sleepy end device

        Returns:
            True: successful to set the data poll period for SED
            False: fail to set the data poll period for SED
        """
        print('%s call setKeepAliveTimeOut' % self.port)
        print(iTimeOut)
        try:
            cmd = self.wpan_cmd_prefix + 'setprop NCP:SleepyPollInterval %s' % str(iTimeOut * 1000)
            print(cmd)
            return self.__sendCommand(cmd)[0] != 'Fail'
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('setKeepAliveTimeOut() Error: ' + str(e))

    def setKeySequenceCounter(self, iKeySequenceValue):
        """ set the Key sequence counter corresponding to Thread network key

        Args:
            iKeySequenceValue: key sequence value

        Returns:
            True: successful to set the key sequence
            False: fail to set the key sequence
        """
        print('%s call setKeySequenceCounter' % self.port)
        print(iKeySequenceValue)
        try:
            # avoid key switch guard timer protection for reference device
            self.__setKeySwitchGuardTime(0)

            cmd = self.wpan_cmd_prefix + 'setprop Network:KeyIndex %s' % str(iKeySequenceValue)
            if self.__sendCommand(cmd)[0] != 'Fail':
                time.sleep(1)
                return True
            else:
                return False
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('setKeySequenceCounter() Error: ' + str(e))

    def getKeySequenceCounter(self):
        """get current Thread Network key sequence"""
        print('%s call getKeySequenceCounter' % self.port)
        keySequence = ''
        keySequence = self.__sendCommand(self.wpan_cmd_prefix + 'getprop -v Network:KeyIndex')[0]
        return keySequence

    def incrementKeySequenceCounter(self, iIncrementValue=1):
        """increment the key sequence with a given value

        Args:
            iIncrementValue: specific increment value to be added

        Returns:
            True: successful to increment the key sequence with a given value
            False: fail to increment the key sequence with a given value
        """
        print('%s call incrementKeySequenceCounter' % self.port)
        print(iIncrementValue)
        currentKeySeq = ''
        try:
            # avoid key switch guard timer protection for reference device
            self.__setKeySwitchGuardTime(0)

            currentKeySeq = self.getKeySequenceCounter()
            keySequence = int(currentKeySeq, 10) + iIncrementValue
            print(keySequence)
            return self.setKeySequenceCounter(keySequence)
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('incrementKeySequenceCounter() Error: ' + str(e))

    def setNetworkDataRequirement(self, eDataRequirement):
        """set whether the Thread device requires the full network data
           or only requires the stable network data

        Args:
            eDataRequirement: is true if requiring the full network data

        Returns:
            True: successful to set the network requirement
        """
        print('%s call setNetworkDataRequirement' % self.port)
        print(eDataRequirement)

        if eDataRequirement == Device_Data_Requirement.ALL_DATA:
            self.networkDataRequirement = 'n'
        return True

    def configExternalRouter(self, P_Prefix, P_stable, R_Preference=0):
        """configure border router with a given external route prefix entry

        Args:
            P_Prefix: IPv6 prefix for the route
            P_Stable: is true if the external route prefix is stable network data
            R_Preference: a two-bit signed integer indicating Router preference
                          1: high
                          0: medium
                         -1: low

        Returns:
            True: successful to configure the border router with a given external route prefix
            False: fail to configure the border router with a given external route prefix
        """
        print('%s call configExternalRouter' % self.port)
        print(P_Prefix)
        prefix = self.__convertIp6PrefixStringToIp6Address(str(P_Prefix))
        try:
            if P_stable:
                cmd = self.wpan_cmd_prefix + 'add-route %s -l 64 -p %d' % (prefix, R_Preference)
            else:
                cmd = self.wpan_cmd_prefix + 'add-route %s -l 64 -p %d -n' % (prefix, R_Preference)
            print(cmd)

            if self.__sendCommand(cmd)[0] != 'Fail':
                return True
            else:
                return False
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('configExternalRouter() Error: ' + str(e))

    def getNeighbouringRouters(self):
        """get neighboring routers information

        Returns:
            neighboring routers' extended address

        @todo: required if as reference device
        """

    def getChildrenInfo(self):
        """get all children information

        Returns:
            children's extended address

        @todo: required if as reference device
        """

    def setXpanId(self, xPanId):
        """set extended PAN ID of Thread Network

        Args:
            xPanId: extended PAN ID in hex format

        Returns:
            True: successful to set the extended PAN ID
            False: fail to set the extended PAN ID
        """
        xpanid = ''
        print('%s call setXpanId' % self.port)
        print(xPanId)
        try:
            if not isinstance(xPanId, str):
                xpanid = self.__convertLongToHex(xPanId, 16)

                cmd = self.wpan_cmd_prefix + 'setprop Network:XPANID %s' % xpanid
                datasetCmd = self.wpan_cmd_prefix + 'setprop Dataset:ExtendedPanId %s' % xpanid
            else:
                xpanid = xPanId
                cmd = self.wpan_cmd_prefix + 'setprop Network:XPANID %s' % xpanid
                datasetCmd = self.wpan_cmd_prefix + 'setprop Dataset:ExtendedPanId %s' % xpanid

            self.xpanId = xpanid
            self.hasActiveDatasetToCommit = True
            return self.__sendCommand(cmd)[0] != 'Fail' and self.__sendCommand(datasetCmd)[0] != 'Fail'
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('setXpanId() Error: ' + str(e))

    def getNeighbouringDevices(self):
        """gets the neighboring devices' extended address to compute the DUT
           extended address automatically

        Returns:
            A list including extended address of neighboring routers, parent
            as well as children
        """
        print('%s call getNeighbouringDevices' % self.port)
        neighbourList = []

        # get parent info
        parentAddr = self.getParentAddress()
        if parentAddr != 0:
            neighbourList.append(parentAddr)

        # get ED/SED children info
        childNeighbours = self.getChildrenInfo()
        if childNeighbours is not None and len(childNeighbours) > 0:
            for entry in childNeighbours:
                neighbourList.append(entry)

        # get neighboring routers info
        routerNeighbours = self.getNeighbouringRouters()
        if routerNeighbours is not None and len(routerNeighbours) > 0:
            for entry in routerNeighbours:
                neighbourList.append(entry)

        print(neighbourList)
        return neighbourList

    def setPartationId(self, partationId):
        """set Thread Network Partition ID

        Args:
            partitionId: partition id to be set by leader

        Returns:
            True: successful to set the Partition ID
            False: fail to set the Partition ID
        """
        print('%s call setPartationId' % self.port)
        print(partationId)

        cmd = self.wpan_cmd_prefix + 'setprop Network:PartitionId %s' % (str(hex(partationId)).rstrip('L'))
        print(cmd)
        return self.__sendCommand(cmd)[0] != 'Fail'

    def getGUA(self, filterByPrefix=None):
        """get expected global unicast IPv6 address of OpenThreadWpan

        note: existing filterByPrefix are string of in lowercase. e.g.
        '2001' or '2001:0db8:0001:0000".

        Args:
            filterByPrefix: a given expected global IPv6 prefix to be matched

        Returns:
            a global IPv6 address
        """
        print('%s call getGUA' % self.port)
        print(filterByPrefix)
        globalAddrs = []
        try:
            # get global addrs set if multiple
            globalAddrs = self.getGlobal()

            if filterByPrefix is None:
                return self.globalAddrs[0]
            else:
                for fullIp in globalAddrs:
                    if fullIp.startswith(filterByPrefix):
                        print('target global %s' % fullIp)
                        return fullIp
                print('no global address matched')
                return str(globalAddrs[0])
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('getGUA() Error: ' + str(e))
            return e

    def getShortAddress(self):
        """get Rloc16 short address of Thread device"""
        print('%s call getShortAddress' % self.port)
        return self.getRloc16()

    def getULA64(self):
        """get mesh local EID of Thread device"""
        print('%s call getULA64' % self.port)
        return self.getMLEID()

    def setMLPrefix(self, sMeshLocalPrefix):
        """set mesh local prefix"""
        print('%s call setMLPrefix' % self.port)
        try:
            cmd = self.wpan_cmd_prefix + 'setprop IPv6:MeshLocalPrefix %s' % sMeshLocalPrefix
            datasetCmd = self.wpan_cmd_prefix + 'setprop Dataset:MeshLocalPrefix %s' % sMeshLocalPrefix
            self.hasActiveDatasetToCommit = True
            return self.__sendCommand(cmd)[0] != 'Fail' and self.__sendCommand(datasetCmd)[0] != 'Fail'
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('setMLPrefix() Error: ' + str(e))

    def getML16(self):
        """get mesh local 16 unicast address (Rloc)"""
        print('%s call getML16' % self.port)
        return self.getRloc()

    def downgradeToDevice(self):
        pass

    def upgradeToRouter(self):
        pass

    def forceSetSlaac(self, slaacAddress):
        """@todo : required if as reference device"""

    def setSleepyNodePollTime(self):
        pass

    def enableAutoDUTObjectFlag(self):
        """set AutoDUTenable flag"""
        print('%s call enableAutoDUTObjectFlag' % self.port)
        self.AutoDUTEnable = True

    def getChildTimeoutValue(self):
        """get child timeout"""
        print('%s call getChildTimeoutValue' % self.port)
        childTimeout = self.__sendCommand(self.wpan_cmd_prefix + 'getprop -v Thread:ChildTimeout')[0]
        return int(childTimeout)

    def diagnosticGet(self, strDestinationAddr, listTLV_ids=[]):
        """@todo : required if as reference device"""

    def diagnosticQuery(self, strDestinationAddr, listTLV_ids=[]):
        """@todo : required if as reference device"""
        self.diagnosticGet(strDestinationAddr, listTLV_ids)

    def diagnosticReset(self, strDestinationAddr, listTLV_ids=[]):
        """@todo : required if as reference device"""

    def startNativeCommissioner(self, strPSKc='GRLPASSPHRASE'):
        # TODO: Support the whole Native Commissioner functionality
        # Currently it only aims to trigger a Discovery Request message to pass
        # Certification test 5.8.4
        print('%s call startNativeCommissioner' % self.port)
        cmd = self.wpan_cmd_prefix + 'joiner --start %s' % (strPSKc)
        print(cmd)
        if self.__sendCommand(cmd)[0] != 'Fail':
            return True
        else:
            return False

    def startCollapsedCommissioner(self):
        """start Collapsed Commissioner

        Returns:
            True: successful to start Commissioner
            False: fail to start Commissioner
        """
        print('%s call startCollapsedCommissioner' % self.port)
        startCmd = self.wpan_cmd_prefix + 'form "%s" -c %s -T router' % (self.networkName, str(self.channel))
        if self.__sendCommand(startCmd) != 'Fail':
            time.sleep(2)
            cmd = self.wpan_cmd_prefix + 'commissioner start'
            print(cmd)
            if self.__sendCommand(cmd)[0] != 'Fail':
                self.isActiveCommissioner = True
                time.sleep(20)  # time for petition process
                return True
        return False

    def setJoinKey(self, strPSKc):
        pass

    def scanJoiner(self, xEUI='*', strPSKd='THREADJPAKETEST'):
        """scan Joiner

        Args:
            xEUI: Joiner's EUI-64
            strPSKd: Joiner's PSKd for commissioning

        Returns:
            True: successful to add Joiner's steering data
            False: fail to add Joiner's steering data
        """
        print('%s call scanJoiner' % self.port)
        if not isinstance(xEUI, str):
            eui64 = self.__convertLongToHex(xEUI, 16)
        else:
            eui64 = xEUI

        # long timeout value to avoid automatic joiner removal (in seconds)
        timeout = 500

        cmd = self.wpan_cmd_prefix + 'commissioner joiner-add "%s" %s %s' % (eui64, str(timeout), strPSKd)
        print(cmd)
        if not self.isActiveCommissioner:
            self.startCollapsedCommissioner()
        if self.__sendCommand(cmd)[0] != 'Fail':
            return True
        else:
            return False

    def setProvisioningUrl(self, strURL='grl.com'):
        """set provisioning Url

        Args:
            strURL: Provisioning Url string

        Returns:
            True: successful to set provisioning Url
            False: fail to set provisioning Url
        """
        print('%s call setProvisioningUrl' % self.port)
        self.provisioningUrl = strURL
        if self.deviceRole == Thread_Device_Role.Commissioner:
            cmd = self.wpan_cmd_prefix + 'setprop Commissioner:ProvisioningUrl %s' % (strURL)
            print(cmd)
            return self.__sendCommand(cmd)[0] != 'Fail'
        return True

    def allowCommission(self):
        """start commissioner candidate petition process

        Returns:
            True: successful to start commissioner candidate petition process
            False: fail to start commissioner candidate petition process
        """
        print('%s call allowCommission' % self.port)
        try:
            cmd = self.wpan_cmd_prefix + 'commissioner start'
            print(cmd)
            if self.isActiveCommissioner:
                return True
            if self.__sendCommand(cmd)[0] != 'Fail':
                self.isActiveCommissioner = True
                time.sleep(40)  # time for petition process and at least one keep alive
                return True
            else:
                return False
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('allowcommission() error: ' + str(e))

    def joinCommissioned(self, strPSKd='THREADJPAKETEST', waitTime=20):
        """start joiner

        Args:
            strPSKd: Joiner's PSKd

        Returns:
            True: successful to start joiner
            False: fail to start joiner
        """
        print('%s call joinCommissioned' % self.port)
        cmd = self.wpan_cmd_prefix + 'joiner --start %s %s' % (strPSKd, self.provisioningUrl)
        print(cmd)
        if self.__sendCommand(cmd)[0] != 'Fail':
            if self.__getJoinerState():
                self.__sendCommand(self.wpan_cmd_prefix + 'joiner --attach')
                time.sleep(30)
                return True
            else:
                return False
        else:
            return False

    def getCommissioningLogs(self):
        """get Commissioning logs

        Returns:
           Commissioning logs
        """
        rawLogs = self.logThread.get()
        ProcessedLogs = []
        payload = []
        while not rawLogs.empty():
            rawLogEach = rawLogs.get()
            print(rawLogEach)
            if '[THCI]' not in rawLogEach:
                continue

            EncryptedPacket = PlatformDiagnosticPacket()
            infoList = rawLogEach.split('[THCI]')[1].split(']')[0].split('|')
            for eachInfo in infoList:
                print(eachInfo)
                info = eachInfo.split('=')
                infoType = info[0].strip()
                infoValue = info[1].strip()
                if 'direction' in infoType:
                    EncryptedPacket.Direction = (PlatformDiagnosticPacket_Direction.IN
                                                 if 'recv' in infoValue else PlatformDiagnosticPacket_Direction.OUT if
                                                 'send' in infoValue else PlatformDiagnosticPacket_Direction.UNKNOWN)
                elif 'type' in infoType:
                    EncryptedPacket.Type = (PlatformDiagnosticPacket_Type.JOIN_FIN_req if 'JOIN_FIN.req' in infoValue
                                            else PlatformDiagnosticPacket_Type.JOIN_FIN_rsp if 'JOIN_FIN.rsp'
                                            in infoValue else PlatformDiagnosticPacket_Type.JOIN_ENT_req if
                                            'JOIN_ENT.ntf' in infoValue else PlatformDiagnosticPacket_Type.JOIN_ENT_rsp
                                            if 'JOIN_ENT.rsp' in infoValue else PlatformDiagnosticPacket_Type.UNKNOWN)
                elif 'len' in infoType:
                    bytesInEachLine = 16
                    EncryptedPacket.TLVsLength = int(infoValue)
                    payloadLineCount = (int(infoValue) + bytesInEachLine - 1) / bytesInEachLine
                    while payloadLineCount > 0:
                        payloadLineCount = payloadLineCount - 1
                        payloadLine = rawLogs.get()
                        payloadSplit = payloadLine.split('|')
                        for block in range(1, 3):
                            payloadBlock = payloadSplit[block]
                            payloadValues = payloadBlock.split(' ')
                            for num in range(1, 9):
                                if '..' not in payloadValues[num]:
                                    payload.append(int(payloadValues[num], 16))

                    EncryptedPacket.TLVs = PlatformPackets.read(EncryptedPacket.Type, payload) if payload != [] else []

            ProcessedLogs.append(EncryptedPacket)
        return ProcessedLogs

    def MGMT_ED_SCAN(self, sAddr, xCommissionerSessionId, listChannelMask, xCount, xPeriod, xScanDuration):
        """send MGMT_ED_SCAN message to a given destinaition.

        Args:
            sAddr: IPv6 destination address for this message
            xCommissionerSessionId: commissioner session id
            listChannelMask: a channel array to indicate which channels to be scanned
            xCount: number of IEEE 802.15.4 ED Scans (milliseconds)
            xPeriod: Period between successive IEEE802.15.4 ED Scans (milliseconds)
            xScanDuration: IEEE 802.15.4 ScanDuration to use when performing an IEEE 802.15.4 ED Scan (milliseconds)

        Returns:
            True: successful to send MGMT_ED_SCAN message.
            False: fail to send MGMT_ED_SCAN message
        """
        print('%s call MGMT_ED_SCAN' % self.port)
        channelMask = ''
        channelMask = self.__ChannelMaskListToStr(listChannelMask)
        try:
            cmd = self.wpan_cmd_prefix + 'commissioner energy-scan %s %s %s %s %s' % (
                channelMask,
                xCount,
                xPeriod,
                xScanDuration,
                sAddr,
            )
            print(cmd)
            return self.__sendCommand(cmd) != 'Fail'
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('MGMT_ED_SCAN() error: ' + str(e))

    def MGMT_PANID_QUERY(self, sAddr, xCommissionerSessionId, listChannelMask, xPanId):
        """send MGMT_PANID_QUERY message to a given destination

        Args:
            xPanId: a given PAN ID to check the conflicts

        Returns:
            True: successful to send MGMT_PANID_QUERY message.
            False: fail to send MGMT_PANID_QUERY message.
        """
        print('%s call MGMT_PANID_QUERY' % self.port)
        panid = ''
        channelMask = ''
        channelMask = self.__ChannelMaskListToStr(listChannelMask)

        if not isinstance(xPanId, str):
            panid = str(hex(xPanId))

        try:
            cmd = self.wpan_cmd_prefix + 'commissioner pan-id-query %s %s %s' % (panid, channelMask, sAddr)
            print(cmd)
            return self.__sendCommand(cmd) != 'Fail'
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('MGMT_PANID_QUERY() error: ' + str(e))

    def MGMT_ANNOUNCE_BEGIN(self, sAddr, xCommissionerSessionId, listChannelMask, xCount, xPeriod):
        """send MGMT_ANNOUNCE_BEGIN message to a given destination

        Returns:
            True: successful to send MGMT_ANNOUNCE_BEGIN message.
            False: fail to send MGMT_ANNOUNCE_BEGIN message.
        """
        print('%s call MGMT_ANNOUNCE_BEGIN' % self.port)
        channelMask = ''
        channelMask = self.__ChannelMaskListToStr(listChannelMask)
        try:
            cmd = self.wpan_cmd_prefix + 'commissioner announce-begin %s %s %s %s' % (
                channelMask,
                xCount,
                xPeriod,
                sAddr,
            )
            print(cmd)
            return self.__sendCommand(cmd) != 'Fail'
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('MGMT_ANNOUNCE_BEGIN() error: ' + str(e))

    def MGMT_ACTIVE_GET(self, Addr='', TLVs=[]):
        """send MGMT_ACTIVE_GET command

        Returns:
            True: successful to send MGMT_ACTIVE_GET
            False: fail to send MGMT_ACTIVE_GET
        """
        print('%s call MGMT_ACTIVE_GET' % self.port)
        try:

            cmd = self.wpan_cmd_prefix + 'dataset mgmt-get-active'

            if len(TLVs) != 0:
                tlvs = ''.join('%02x' % tlv for tlv in TLVs)
                setTLVCmd = self.wpan_cmd_prefix + 'setprop Dataset:RawTlvs ' + tlvs
                if self.__sendCommand(setTLVCmd)[0] == 'Fail':
                    return False
            else:
                if self.__sendCommand(self.wpan_cmd_prefix + 'dataset erase')[0] == 'Fail':
                    return False

            if Addr != '':
                setAddressCmd = self.wpan_cmd_prefix + 'setprop Dataset:DestIpAddress ' + Addr
                if self.__sendCommand(setAddressCmd)[0] == 'Fail':
                    return False

            print(cmd)

            return self.__sendCommand(cmd)[0] != 'Fail'

        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('MGMT_ACTIVE_GET() Error: ' + str(e))

    def MGMT_ACTIVE_SET(
        self,
        sAddr='',
        xCommissioningSessionId=None,
        listActiveTimestamp=None,
        listChannelMask=None,
        xExtendedPanId=None,
        sNetworkName=None,
        sPSKc=None,
        listSecurityPolicy=None,
        xChannel=None,
        sMeshLocalPrefix=None,
        xMasterKey=None,
        xPanId=None,
        xTmfPort=None,
        xSteeringData=None,
        xBorderRouterLocator=None,
        BogusTLV=None,
        xDelayTimer=None,
    ):
        """send MGMT_ACTIVE_SET command

        Returns:
            True: successful to send MGMT_ACTIVE_SET
            False: fail to send MGMT_ACTIVE_SET
        """
        print('%s call MGMT_ACTIVE_SET' % self.port)
        try:
            cmd = self.wpan_cmd_prefix + 'dataset mgmt-set-active'

            if self.__sendCommand(self.wpan_cmd_prefix + 'dataset erase')[0] == 'Fail':
                return False

            if listActiveTimestamp is not None:
                sActiveTimestamp = '%016x' % listActiveTimestamp[0]
                setActiveTimeCmd = self.wpan_cmd_prefix + 'setprop Dataset:ActiveTimestamp ' + sActiveTimestamp
                if self.__sendCommand(setActiveTimeCmd)[0] == 'Fail':
                    return False

            if xExtendedPanId is not None:
                xpanid = self.__convertLongToHex(xExtendedPanId, 16)
                setExtendedPanIdCmd = self.wpan_cmd_prefix + 'setprop Dataset:ExtendedPanId ' + xpanid
                if self.__sendCommand(setExtendedPanIdCmd)[0] == 'Fail':
                    return False

            if sNetworkName is not None:
                setNetworkNameCmd = self.wpan_cmd_prefix + 'setprop Dataset:NetworkName ' + str(sNetworkName)
                if self.__sendCommand(setNetworkNameCmd)[0] == 'Fail':
                    return False

            if xChannel is not None:
                setChannelCmd = self.wpan_cmd_prefix + 'setprop Dataset:Channel ' + str(xChannel)
                if self.__sendCommand(setChannelCmd)[0] == 'Fail':
                    return False

            if sMeshLocalPrefix is not None:
                setMLPrefixCmd = self.wpan_cmd_prefix + 'setprop Dataset:MeshLocalPrefix ' + str(sMeshLocalPrefix)
                if self.__sendCommand(setMLPrefixCmd)[0] == 'Fail':
                    return False

            if xMasterKey is not None:
                key = self.__convertLongToHex(xMasterKey, 32)
                setNetworkKeyCmd = self.wpan_cmd_prefix + 'setprop Dataset:NetworkKey ' + key
                if self.__sendCommand(setNetworkKeyCmd)[0] == 'Fail':
                    return False

            if xPanId is not None:
                setPanIdCmd = self.wpan_cmd_prefix + 'setprop Dataset:PanId ' + str(xPanId)
                if self.__sendCommand(setPanIdCmd)[0] == 'Fail':
                    return False

            if listChannelMask is not None:
                setChannelMaskCmd = (self.wpan_cmd_prefix + 'setprop Dataset:ChannelMaskPage0 ' + '0x' +
                                     self.__convertLongToHex(self.__convertChannelMask(listChannelMask)))
                if self.__sendCommand(setChannelMaskCmd)[0] == 'Fail':
                    return False

            if (sPSKc is not None or listSecurityPolicy is not None or xCommissioningSessionId is not None or
                    xTmfPort is not None or xSteeringData is not None or xBorderRouterLocator is not None or
                    BogusTLV is not None):
                setRawTLVCmd = self.wpan_cmd_prefix + 'setprop Dataset:RawTlvs '

            if sPSKc is not None:
                setRawTLVCmd += '0410'
                stretchedPskc = Thread_PBKDF2.get(sPSKc, ModuleHelper.Default_XpanId, ModuleHelper.Default_NwkName)
                pskc = '%x' % stretchedPskc

                if len(pskc) < 32:
                    pskc = pskc.zfill(32)

                setRawTLVCmd += pskc

            if listSecurityPolicy is not None:
                setRawTLVCmd += '0c03'

                rotationTime = 0
                policyBits = 0

                # previous passing way listSecurityPolicy=[True, True, 3600,
                # False, False, True]
                if len(listSecurityPolicy) == 6:
                    rotationTime = listSecurityPolicy[2]

                    # the last three reserved bits must be 1
                    policyBits = 0b00000111

                    if listSecurityPolicy[0]:
                        policyBits = policyBits | 0b10000000
                    if listSecurityPolicy[1]:
                        policyBits = policyBits | 0b01000000
                    if listSecurityPolicy[3]:
                        policyBits = policyBits | 0b00100000
                    if listSecurityPolicy[4]:
                        policyBits = policyBits | 0b00010000
                    if listSecurityPolicy[5]:
                        policyBits = policyBits | 0b00001000
                else:
                    # new passing way listSecurityPolicy=[3600, 0b11001111]
                    rotationTime = listSecurityPolicy[0]
                    policyBits = listSecurityPolicy[1]

                policy = str(hex(rotationTime))[2:]

                if len(policy) < 4:
                    policy = policy.zfill(4)

                setRawTLVCmd += policy

                setRawTLVCmd += str(hex(policyBits))[2:]

            if xCommissioningSessionId is not None:
                setRawTLVCmd += '0b02'
                sessionid = str(hex(xCommissioningSessionId))[2:]

                if len(sessionid) < 4:
                    sessionid = sessionid.zfill(4)

                setRawTLVCmd += sessionid

            if xBorderRouterLocator is not None:
                setRawTLVCmd += '0902'
                locator = str(hex(xBorderRouterLocator))[2:]

                if len(locator) < 4:
                    locator = locator.zfill(4)

                setRawTLVCmd += locator

            if xSteeringData is not None:
                steeringData = self.__convertLongToHex(xSteeringData)
                setRawTLVCmd += '08' + str(len(steeringData) / 2).zfill(2)
                setRawTLVCmd += steeringData

            if BogusTLV is not None:
                setRawTLVCmd += '8202aa55'

            print(setRawTLVCmd)
            print(cmd)

            if self.__sendCommand(setRawTLVCmd)[0] == 'Fail':
                return False

            return self.__sendCommand(cmd)[0] != 'Fail'

        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('MGMT_ACTIVE_SET() Error: ' + str(e))

    def MGMT_PENDING_GET(self, Addr='', TLVs=[]):
        """send MGMT_PENDING_GET command

        Returns:
            True: successful to send MGMT_PENDING_GET
            False: fail to send MGMT_PENDING_GET
        """
        print('%s call MGMT_PENDING_GET' % self.port)
        try:
            cmd = self.wpan_cmd_prefix + 'dataset mgmt-get-pending'

            if len(TLVs) != 0:
                tlvs = ''.join('%02x' % tlv for tlv in TLVs)
                setTLVCmd = self.wpan_cmd_prefix + 'setprop Dataset:RawTlvs ' + tlvs
                if self.__sendCommand(setTLVCmd)[0] == 'Fail':
                    return False
            else:
                if self.__sendCommand(self.wpan_cmd_prefix + 'dataset erase')[0] == 'Fail':
                    return False

            if Addr != '':
                setAddressCmd = self.wpan_cmd_prefix + 'setprop Dataset:DestIpAddress ' + Addr
                if self.__sendCommand(setAddressCmd)[0] == 'Fail':
                    return False

            print(cmd)

            return self.__sendCommand(cmd)[0] != 'Fail'

        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('MGMT_PENDING_GET() Error: ' + str(e))

    def MGMT_PENDING_SET(
        self,
        sAddr='',
        xCommissionerSessionId=None,
        listPendingTimestamp=None,
        listActiveTimestamp=None,
        xDelayTimer=None,
        xChannel=None,
        xPanId=None,
        xMasterKey=None,
        sMeshLocalPrefix=None,
        sNetworkName=None,
    ):
        """send MGMT_PENDING_SET command

        Returns:
            True: successful to send MGMT_PENDING_SET
            False: fail to send MGMT_PENDING_SET
        """
        print('%s call MGMT_PENDING_SET' % self.port)
        try:
            cmd = self.wpan_cmd_prefix + 'dataset mgmt-set-pending'
            if self.__sendCommand(self.wpan_cmd_prefix + 'dataset erase')[0] == 'Fail':
                return False

            if listPendingTimestamp is not None:
                sActiveTimestamp = '%016x' % listPendingTimestamp[0]
                setPendingTimeCmd = self.wpan_cmd_prefix + 'setprop Dataset:PendingTimestamp ' + sActiveTimestamp
                if self.__sendCommand(setPendingTimeCmd)[0] == 'Fail':
                    return False

            if listActiveTimestamp is not None:
                sActiveTimestamp = '%016x' % listActiveTimestamp[0]
                setActiveTimeCmd = self.wpan_cmd_prefix + 'setprop Dataset:ActiveTimestamp ' + sActiveTimestamp
                if self.__sendCommand(setActiveTimeCmd)[0] == 'Fail':
                    return False

            if xDelayTimer is not None:
                setDelayTimerCmd = self.wpan_cmd_prefix + 'setprop Dataset:Delay ' + str(xDelayTimer)
                if self.__sendCommand(setDelayTimerCmd)[0] == 'Fail':
                    return False

            if sNetworkName is not None:
                setNetworkNameCmd = self.wpan_cmd_prefix + 'setprop Dataset:NetworkName ' + str(sNetworkName)
                if self.__sendCommand(setNetworkNameCmd)[0] == 'Fail':
                    return False

            if xChannel is not None:
                setChannelCmd = self.wpan_cmd_prefix + 'setprop Dataset:Channel ' + str(xChannel)
                if self.__sendCommand(setChannelCmd)[0] == 'Fail':
                    return False

            if sMeshLocalPrefix is not None:
                setMLPrefixCmd = self.wpan_cmd_prefix + 'setprop Dataset:MeshLocalPrefix ' + str(sMeshLocalPrefix)
                if self.__sendCommand(setMLPrefixCmd)[0] == 'Fail':
                    return False

            if xMasterKey is not None:
                key = self.__convertLongToHex(xMasterKey, 32)
                setNetworkKeyCmd = self.wpan_cmd_prefix + 'setprop Dataset:NetworkKey ' + key
                if self.__sendCommand(setNetworkKeyCmd)[0] == 'Fail':
                    return False

            if xPanId is not None:
                setPanIdCmd = self.wpan_cmd_prefix + 'setprop Dataset:PanId ' + str(xPanId)
                if self.__sendCommand(setPanIdCmd)[0] == 'Fail':
                    return False

            if xCommissionerSessionId is not None:
                print('not handle xCommissionerSessionId')

            print(cmd)

            return self.__sendCommand(cmd)[0] != 'Fail'

        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('MGMT_PENDING_SET() Error: ' + str(e))

    def MGMT_COMM_GET(self, Addr='ff02::1', TLVs=[]):
        """send MGMT_COMM_GET command

        Returns:
            True: successful to send MGMT_COMM_GET
            False: fail to send MGMT_COMM_GET
        """
        print('%s call MGMT_COMM_GET' % self.port)
        try:
            cmd = self.wpan_cmd_prefix + 'commissioner mgmt-get '
            print('TLVs:')
            print(TLVs)

            if len(TLVs) != 0:
                tlvs = ''.join('%02x' % tlv for tlv in TLVs)
                cmd += tlvs

            print(cmd)

            return self.__sendCommand(cmd)[0] != 'Fail'

        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('MGMT_COMM_GET() Error: ' + str(e))

    def MGMT_COMM_SET(
        self,
        Addr='ff02::1',
        xCommissionerSessionID=None,
        xSteeringData=None,
        xBorderRouterLocator=None,
        xChannelTlv=None,
        ExceedMaxPayload=False,
    ):
        """send MGMT_COMM_SET command

        Returns:
            True: successful to send MGMT_COMM_SET
            False: fail to send MGMT_COMM_SET
        """
        print('%s call MGMT_COMM_SET' % self.port)
        try:
            cmd = self.wpan_cmd_prefix + 'commissioner mgmt-set '
            print('-------------------------------')
            print(xCommissionerSessionID)
            print(xSteeringData)
            print(str(xSteeringData) + '   ' + str(hex(xSteeringData)[2:]))
            print(xBorderRouterLocator)
            print(xChannelTlv)
            print(ExceedMaxPayload)
            print('-------------------------------')

            if xCommissionerSessionID is not None:
                # use assigned session id
                cmd += '0b02' + str(xCommissionerSessionID)
            elif xCommissionerSessionID is None:
                # use original session id
                if self.isActiveCommissioner is True:
                    cmd += '0b02' + self.__lstrip0x(self.__getCommissionerSessionId())
                else:
                    pass

            if xSteeringData is not None:
                cmd += '08' + str(len(hex(xSteeringData)[2:])) + str(hex(xSteeringData)[2:])

            if xBorderRouterLocator is not None:
                cmd += '0902' + str(hex(xBorderRouterLocator))

            if xChannelTlv is not None:
                cmd += '000300' + '%04x' % xChannelTlv

            print(cmd)

            return self.__sendCommand(cmd)[0] != 'Fail'

        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('MGMT_COMM_SET() Error: ' + str(e))

    def setActiveDataset(self, listActiveDataset=[]):
        print('%s call setActiveDataset' % self.port)

    def setCommisionerMode(self):
        print('%s call setCommissionerMode' % self.port)

    def setPSKc(self, strPSKc):
        print('%s call setPSKc' % self.port)
        try:
            cmd = self.wpan_cmd_prefix + 'setprop Network:PSKc %s' % strPSKc
            datasetCmd = self.wpan_cmd_prefix + 'setprop Dataset:PSKc %s' % strPSKc
            self.hasActiveDatasetToCommit = True
            return self.__sendCommand(cmd)[0] != 'Fail' and self.__sendCommand(datasetCmd)[0] != 'Fail'
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('setPSKc() Error: ' + str(e))

    def setActiveTimestamp(self, xActiveTimestamp):
        print('%s call setActiveTimestamp' % self.port)
        try:
            sActiveTimestamp = str(xActiveTimestamp)
            if len(sActiveTimestamp) < 16:
                sActiveTimestamp = sActiveTimestamp.zfill(16)
            self.activetimestamp = sActiveTimestamp
            cmd = self.wpan_cmd_prefix + 'setprop Dataset:ActiveTimestamp %s' % sActiveTimestamp
            self.hasActiveDatasetToCommit = True
            return self.__sendCommand(cmd)[0] != 'Fail'
        except Exception as e:
            ModuleHelper.WriteIntoDebugLogger('setActiveTimestamp() Error: ' + str(e))

    def setUdpJoinerPort(self, portNumber):
        """set Joiner UDP Port

        Args:
            portNumber: Joiner UDP Port number

        Returns:
            True: successful to set Joiner UDP Port
            False: fail to set Joiner UDP Port

        @todo : required if as reference device
        """

    def commissionerUnregister(self):
        """stop commissioner

        Returns:
            True: successful to stop commissioner
            False: fail to stop commissioner
        """
        print('%s call commissionerUnregister' % self.port)
        cmd = self.wpan_cmd_prefix + 'commissioner stop'
        print(cmd)
        if self.__sendCommand(cmd)[0] != 'Fail':
            self.isActiveCommissioner = False
            return True
        else:
            return False

    def sendBeacons(self, sAddr, xCommissionerSessionId, listChannelMask, xPanId):
        print('%s call sendBeacons' % self.port)
        self._sendline(self.wpan_cmd_prefix + 'scan')
        return True

    def updateRouterStatus(self):
        """force update to router as if there is child id request
            @todo : required if as reference device
        """

    def setRouterThresholdValues(self, upgradeThreshold, downgradeThreshold):
        print('%s call setRouterThresholdValues' % self.port)
        self.__setRouterUpgradeThreshold(upgradeThreshold)
        self.__setRouterDowngradeThreshold(downgradeThreshold)

    def setMinDelayTimer(self, iSeconds):
        pass

    def ValidateDeviceFirmware(self):
        print('%s call ValidateDeviceFirmware' % self.port)
        if 'OPENTHREAD' in self.UIStatusMsg:
            return True
        else:
            return False

    @staticmethod
    def __lstrip0x(s):
        """strip 0x at the beginning of a hex string if it exists

        Args:
            s: hex string

        Returns:
            hex string with leading 0x stripped
        """
        if s.startswith('0x'):
            s = s[2:]

        return s
