2739 lines
102 KiB
Python
2739 lines
102 KiB
Python
|
|
#!/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 addresss 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 scaned
|
||
|
|
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
|