[thci] serial handling improvements (#7113)

Improve handling of timeouts, exceptions and logging for device serial
communication.

Also include other minor syntax editions.
This commit is contained in:
Eduardo Montoya
2021-10-28 11:24:54 -07:00
committed by GitHub
parent 05e59ade17
commit 76cdf9dc7f
+177 -106
View File
@@ -42,6 +42,7 @@ from abc import abstractmethod
import serial import serial
from Queue import Queue from Queue import Queue
from serial.serialutil import SerialException
TESTHARNESS_1_1 = '1.1' TESTHARNESS_1_1 = '1.1'
TESTHARNESS_1_2 = '1.2' TESTHARNESS_1_2 = '1.2'
@@ -76,6 +77,8 @@ if TESTHARNESS_VERSION == TESTHARNESS_1_2:
from IThci import IThci from IThci import IThci
ZEPHYR_PREFIX = 'ot '
LINESEPX = re.compile(r'\r\n|\n') LINESEPX = re.compile(r'\r\n|\n')
"""regex: used to split lines""" """regex: used to split lines"""
@@ -186,6 +189,7 @@ class OpenThreadTHCI(object):
firmwarePrefix = 'OPENTHREAD/' firmwarePrefix = 'OPENTHREAD/'
DOMAIN_NAME = 'Thread' DOMAIN_NAME = 'Thread'
MLR_TIMEOUT_MIN = 300 MLR_TIMEOUT_MIN = 300
NETWORK_ATTACHMENT_TIMEOUT = 10
IsBorderRouter = False IsBorderRouter = False
IsBackboneRouter = False IsBackboneRouter = False
@@ -281,29 +285,35 @@ class OpenThreadTHCI(object):
self.logThreadStatus != self.logStatus['stop']): self.logThreadStatus != self.logStatus['stop']):
pass pass
self.__sendCommand(cmd) try:
line = None self.__sendCommand(cmd)
response = [] response = []
t_end = time.time() + timeout t_end = time.time() + timeout
while time.time() < t_end: while time.time() < t_end:
line = self.__readCliLine() line = self.__readCliLine()
if line is None: if line is None:
time.sleep(0.01) time.sleep(0.01)
continue continue
self.log("readline: %s", line) self.log("readline: %s", line)
response.append(line) response.append(line)
if line == 'Done': if line.endswith('Done'):
break break
else:
m = OpenThreadTHCI._COMMAND_OUTPUT_ERROR_PATTERN.match(line)
if m is not None:
code, msg = m.groups()
raise CommandError(int(code), msg)
else: else:
m = OpenThreadTHCI._COMMAND_OUTPUT_ERROR_PATTERN.match(line) raise Exception('%s: failed to find end of response: %s' % (self, response))
if m is not None:
code, msg = m.groups() except SerialException as e:
raise CommandError(int(code), msg) self.log('__executeCommand() Error: ' + str(e))
else: self._disconnect()
raise Exception('%s: failed to find end of response: %s' % (self, response)) self._connect()
raise e
return response return response
@@ -550,7 +560,7 @@ class OpenThreadTHCI(object):
else: else:
self.hasActiveDatasetToCommit = False self.hasActiveDatasetToCommit = False
# restore allowlist/denylist address filter mode if rejoin after # Restore allowlist/denylist address filter mode if rejoin after
# reset # reset
if self.isPowerDown: if self.isPowerDown:
if self._addressfilterMode == 'allowlist': if self._addressfilterMode == 'allowlist':
@@ -618,9 +628,19 @@ class OpenThreadTHCI(object):
True: OpenThread is running True: OpenThread is running
False: OpenThread is not running False: OpenThread is not running
""" """
self.log('call isOpenThreadRunning')
return self.__executeCommand('state')[0] != 'disabled' return self.__executeCommand('state')[0] != 'disabled'
@watched
def __isDeviceAttached(self):
"""check whether or not OpenThread is running
Returns:
True: OpenThread is running
False: OpenThread is not running
"""
detached_states = ["detached", "disabled"]
return self.__executeCommand('state')[0] not in detached_states
# rloc16 might be hex string or integer, need to return actual allocated # rloc16 might be hex string or integer, need to return actual allocated
# router id # router id
def __convertRlocToRouterId(self, xRloc16): def __convertRlocToRouterId(self, xRloc16):
@@ -632,13 +652,14 @@ class OpenThreadTHCI(object):
Returns: Returns:
actual router id allocated by leader actual router id allocated by leader
""" """
routerList = []
routerList = self.__executeCommand('router list')[0].split() routerList = self.__executeCommand('router list')[0].split()
rloc16 = None
routerid = None
print(routerList) print(routerList)
print(xRloc16) print(xRloc16)
for index in routerList: for index in routerList:
router = []
cmd = 'router %s' % index cmd = 'router %s' % index
router = self.__executeCommand(cmd) router = self.__executeCommand(cmd)
@@ -898,7 +919,6 @@ class OpenThreadTHCI(object):
""" """
print('%s call setMAC' % self) print('%s call setMAC' % self)
print(xEUI) print(xEUI)
address64 = ''
try: try:
if not isinstance(xEUI, str): if not isinstance(xEUI, str):
address64 = self.__convertLongToHex(xEUI, 16) address64 = self.__convertLongToHex(xEUI, 16)
@@ -1008,9 +1028,9 @@ class OpenThreadTHCI(object):
True: successful to set the Thread network key True: successful to set the Thread network key
False: fail to set the Thread network key False: fail to set the Thread network key
""" """
networkKey = ''
print('%s call setNetworkKey' % self) print('%s call setNetworkKey' % self)
print(key) print(key)
try: try:
if not isinstance(key, str): if not isinstance(key, str):
networkKey = self.__convertLongToHex(key, 32) networkKey = self.__convertLongToHex(key, 32)
@@ -1238,17 +1258,44 @@ class OpenThreadTHCI(object):
# start OpenThread # start OpenThread
self.__startOpenThread() self.__startOpenThread()
self.sleep(3) self.wait_for_attach_to_the_network(expected_role=eRoleId,
timeout=self.NETWORK_ATTACHMENT_TIMEOUT,
if self._update_router_status and eRoleId == Thread_Device_Role.Router: raise_assert=True)
self.__updateRouterStatus()
self.sleep(5) # increase delay temporally (+5s) to remedy TH's delay updates
return True return True
except Exception as e: except Exception as e:
ModuleHelper.WriteIntoDebugLogger('joinNetwork() Error: ' + str(e)) ModuleHelper.WriteIntoDebugLogger('joinNetwork() Error: ' + str(e))
def wait_for_attach_to_the_network(self, expected_role, timeout, raise_assert=False):
start_time = time.time()
while time.time() < start_time + timeout:
time.sleep(0.3)
if self.__isDeviceAttached():
break
else:
if raise_assert:
raise AssertionError("OT device {} could not attach to the network after {} s of timeout.".format(
self, timeout))
else:
return False
if self._update_router_status:
self.__updateRouterStatus()
if expected_role == Thread_Device_Role.Router:
while time.time() < start_time + timeout:
time.sleep(0.3)
if self.getDeviceRole() == "router":
break
else:
if raise_assert:
raise AssertionError("OT Router {} could not attach to the network after {} s of timeout.".format(
self, timeout * 2))
else:
return False
return True
@API @API
def getNetworkFragmentID(self): def getNetworkFragmentID(self):
"""get current partition id of Thread Network Partition from LeaderData """get current partition id of Thread Network Partition from LeaderData
@@ -1261,7 +1308,6 @@ class OpenThreadTHCI(object):
print('OpenThread is not running') print('OpenThread is not running')
return None return None
leaderData = []
leaderData = self.__executeCommand('leaderdata') leaderData = self.__executeCommand('leaderdata')
return int(leaderData[0].split()[2], 16) return int(leaderData[0].split()[2], 16)
@@ -1273,7 +1319,7 @@ class OpenThreadTHCI(object):
The extended address of parent in hex format The extended address of parent in hex format
""" """
print('%s call getParentAddress' % self) print('%s call getParentAddress' % self)
parentInfo = [] eui = None
parentInfo = self.__executeCommand('parent') parentInfo = self.__executeCommand('parent')
for line in parentInfo: for line in parentInfo:
@@ -1295,6 +1341,11 @@ class OpenThreadTHCI(object):
"""power down the Thread device""" """power down the Thread device"""
print('%s call powerDown' % self) print('%s call powerDown' % self)
self.__sendCommand('reset', expectEcho=False) self.__sendCommand('reset', expectEcho=False)
if not self.IsBorderRouter:
self._disconnect()
self._connect()
self.isPowerDown = True self.isPowerDown = True
@API @API
@@ -1306,6 +1357,28 @@ class OpenThreadTHCI(object):
if not self.__isOpenThreadRunning(): if not self.__isOpenThreadRunning():
self.__startOpenThread() self.__startOpenThread()
def reset_and_wait_for_connection(self, timeout=3):
print("Waiting after reset timeout: {} s".format(timeout))
start_time = time.time()
self.__sendCommand('reset', expectEcho=False)
self.isPowerDown = True
while time.time() < start_time + timeout:
time.sleep(0.3)
if TESTHARNESS_VERSION == TESTHARNESS_1_2 and not self.IsBorderRouter:
self._disconnect()
self._connect()
try:
self.__executeCommand('state', timeout=0.1)
break
except Exception:
continue
else:
raise AssertionError("Could not connect with OT device {} after reset.".format(self))
if self.deviceRole == Thread_Device_Role.SED:
self.__setPollPeriod(self.__sedPollPeriod)
@API @API
def reboot(self): def reboot(self):
"""reset and rejoin to Thread Network without any timeout """reset and rejoin to Thread Network without any timeout
@@ -1316,28 +1389,35 @@ class OpenThreadTHCI(object):
""" """
print('%s call reboot' % self) print('%s call reboot' % self)
try: try:
self.__sendCommand('reset', expectEcho=False) self.reset_and_wait_for_connection()
self.isPowerDown = True
self.sleep(3)
self.__startOpenThread() self.__startOpenThread()
self.sleep(3) return self.wait_for_attach_to_the_network(expected_role="", timeout=self.NETWORK_ATTACHMENT_TIMEOUT)
if self.__executeCommand('state')[0] == 'disabled':
print('[FAIL] reboot')
return False
else:
return True
except Exception as e: except Exception as e:
ModuleHelper.WriteIntoDebugLogger('reboot() Error: ' + str(e)) ModuleHelper.WriteIntoDebugLogger('reboot() Error: ' + str(e))
@API
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)
try:
self.reset_and_wait_for_connection(timeout=timeout)
self.__startOpenThread()
return self.wait_for_attach_to_the_network(expected_role="", timeout=self.NETWORK_ATTACHMENT_TIMEOUT)
except Exception as e:
ModuleHelper.WriteIntoDebugLogger('resetAndRejoin() Error: ' + str(e))
@API @API
def ping(self, strDestination, ilength=0, hop_limit=64, timeout=5): def ping(self, strDestination, ilength=0, hop_limit=64, timeout=5):
""" send ICMPv6 echo request with a given length/hoplimit to a unicast """ send ICMPv6 echo request with a given length/hoplimit to a unicast
destination address destination address
TODO: add hop_limit support
Args: Args:
srcDestination: the unicast destination address of ICMPv6 echo request srcDestination: the unicast destination address of ICMPv6 echo request
ilength: the size of ICMPv6 echo request payload ilength: the size of ICMPv6 echo request payload
@@ -1404,17 +1484,23 @@ class OpenThreadTHCI(object):
self._deviceBeforeReset() self._deviceBeforeReset()
self.__sendCommand('factoryreset', expectEcho=False) self.__sendCommand('factoryreset', expectEcho=False)
timeout = 10
start_time = time.time() start_time = time.time()
while time.time() < start_time + 10: while time.time() < start_time + timeout:
time.sleep(0.3) time.sleep(0.3)
if TESTHARNESS_VERSION == TESTHARNESS_1_2 and not self.IsBorderRouter:
self._disconnect()
self._connect()
try: try:
self.__executeCommand('state', timeout=0.1) self.__executeCommand('state', timeout=0.1)
break break
except Exception: except Exception:
continue continue
else:
raise AssertionError("Could not connect with OT device {} after reset.".format(self))
self.log('factoryreset finished in %dms', int(time.time() - start_time)) self.log('factoryreset finished within 10s timeout.')
self._deviceAfterReset() self._deviceAfterReset()
@API @API
@@ -1430,7 +1516,6 @@ class OpenThreadTHCI(object):
""" """
print('%s call removeRouter' % self) print('%s call removeRouter' % self)
print(xRouterId) print(xRouterId)
routerId = ''
routerId = self.__convertRlocToRouterId(xRouterId) routerId = self.__convertRlocToRouterId(xRouterId)
print(routerId) print(routerId)
@@ -1648,37 +1733,6 @@ class OpenThreadTHCI(object):
except Exception as e: except Exception as e:
ModuleHelper.WriteIntoDebugLogger('removeRouterPrefix() Error: ' + str(e)) ModuleHelper.WriteIntoDebugLogger('removeRouterPrefix() Error: ' + str(e))
@API
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)
print(timeout)
try:
self.__sendCommand('reset', expectEcho=False)
self.isPowerDown = True
self.sleep(timeout)
if self.deviceRole == Thread_Device_Role.SED:
self.__setPollPeriod(self.__sedPollPeriod)
self.__startOpenThread()
self.sleep(3)
if self.__executeCommand('state')[0] == 'disabled':
print('[FAIL] reset and rejoin')
return False
return True
except Exception as e:
ModuleHelper.WriteIntoDebugLogger('resetAndRejoin() Error: ' + str(e))
@API @API
def configBorderRouter( def configBorderRouter(
self, self,
@@ -1849,7 +1903,6 @@ class OpenThreadTHCI(object):
def getKeySequenceCounter(self): def getKeySequenceCounter(self):
"""get current Thread Network key sequence""" """get current Thread Network key sequence"""
print('%s call getKeySequenceCounter' % self) print('%s call getKeySequenceCounter' % self)
keySequence = ''
keySequence = self.__executeCommand('keysequence counter')[0] keySequence = self.__executeCommand('keysequence counter')[0]
return keySequence return keySequence
@@ -1866,7 +1919,6 @@ class OpenThreadTHCI(object):
""" """
print('%s call incrementKeySequenceCounter' % self) print('%s call incrementKeySequenceCounter' % self)
print(iIncrementValue) print(iIncrementValue)
currentKeySeq = ''
try: try:
# avoid key switch guard timer protection for reference device # avoid key switch guard timer protection for reference device
self.__setKeySwitchGuardTime(0) self.__setKeySwitchGuardTime(0)
@@ -1913,6 +1965,7 @@ class OpenThreadTHCI(object):
""" """
print('%s call configExternalRouter' % self) print('%s call configExternalRouter' % self)
print(P_Prefix) print(P_Prefix)
prf = ''
stable = '' stable = ''
prefix = self.__convertIp6PrefixStringToIp6Address(str(P_Prefix)) prefix = self.__convertIp6PrefixStringToIp6Address(str(P_Prefix))
try: try:
@@ -1948,7 +2001,6 @@ class OpenThreadTHCI(object):
print('%s call getNeighbouringRouters' % self) print('%s call getNeighbouringRouters' % self)
try: try:
routerInfo = [] routerInfo = []
routerList = []
routerList = self.__executeCommand('router list')[0].split() routerList = self.__executeCommand('router list')[0].split()
print(routerList) print(routerList)
@@ -1988,7 +2040,11 @@ class OpenThreadTHCI(object):
Returns: Returns:
children's extended address children's extended address
""" """
eui = None
rloc16 = None
print('%s call getChildrenInfo' % self) print('%s call getChildrenInfo' % self)
try: try:
childrenInfoAll = [] childrenInfoAll = []
childrenInfo = {'EUI': 0, 'Rloc16': 0, 'MLEID': ''} childrenInfo = {'EUI': 0, 'Rloc16': 0, 'MLEID': ''}
@@ -2212,7 +2268,7 @@ class OpenThreadTHCI(object):
return int(childTimeout) return int(childTimeout)
@API @API
def diagnosticGet(self, strDestinationAddr, listTLV_ids=[]): def diagnosticGet(self, strDestinationAddr, listTLV_ids=()):
if not listTLV_ids: if not listTLV_ids:
return return
@@ -2228,7 +2284,7 @@ class OpenThreadTHCI(object):
return self.__sendCommand(cmd, expectEcho=False) return self.__sendCommand(cmd, expectEcho=False)
@API @API
def diagnosticReset(self, strDestinationAddr, listTLV_ids=[]): def diagnosticReset(self, strDestinationAddr, listTLV_ids=()):
if not listTLV_ids: if not listTLV_ids:
return return
@@ -2244,7 +2300,7 @@ class OpenThreadTHCI(object):
return self.__executeCommand(cmd) return self.__executeCommand(cmd)
@API @API
def diagnosticQuery(self, strDestinationAddr, listTLV_ids=[]): def diagnosticQuery(self, strDestinationAddr, listTLV_ids=()):
self.diagnosticGet(strDestinationAddr, listTLV_ids) self.diagnosticGet(strDestinationAddr, listTLV_ids)
@API @API
@@ -2308,7 +2364,9 @@ class OpenThreadTHCI(object):
""" """
print('%s call startCollapsedCommissioner' % self) print('%s call startCollapsedCommissioner' % self)
if self.__startOpenThread(): if self.__startOpenThread():
self.sleep(20) self.wait_for_attach_to_the_network(expected_role=self.deviceRole,
timeout=self.NETWORK_ATTACHMENT_TIMEOUT,
raise_assert=True)
cmd = 'commissioner start' cmd = 'commissioner start'
print(cmd) print(cmd)
if self.__executeCommand(cmd)[-1] == 'Done': if self.__executeCommand(cmd)[-1] == 'Done':
@@ -2520,7 +2578,6 @@ class OpenThreadTHCI(object):
False: fail to send MGMT_ED_SCAN message False: fail to send MGMT_ED_SCAN message
""" """
print('%s call MGMT_ED_SCAN' % self) print('%s call MGMT_ED_SCAN' % self)
channelMask = ''
channelMask = '0x' + self.__convertLongToHex(self.__convertChannelMask(listChannelMask)) channelMask = '0x' + self.__convertLongToHex(self.__convertChannelMask(listChannelMask))
try: try:
cmd = 'commissioner energy %s %s %s %s %s' % ( cmd = 'commissioner energy %s %s %s %s %s' % (
@@ -2548,7 +2605,6 @@ class OpenThreadTHCI(object):
""" """
print('%s call MGMT_PANID_QUERY' % self) print('%s call MGMT_PANID_QUERY' % self)
panid = '' panid = ''
channelMask = ''
channelMask = '0x' + self.__convertLongToHex(self.__convertChannelMask(listChannelMask)) channelMask = '0x' + self.__convertLongToHex(self.__convertChannelMask(listChannelMask))
if not isinstance(xPanId, str): if not isinstance(xPanId, str):
@@ -2570,7 +2626,6 @@ class OpenThreadTHCI(object):
False: fail to send MGMT_ANNOUNCE_BEGIN message. False: fail to send MGMT_ANNOUNCE_BEGIN message.
""" """
print('%s call MGMT_ANNOUNCE_BEGIN' % self) print('%s call MGMT_ANNOUNCE_BEGIN' % self)
channelMask = ''
channelMask = '0x' + self.__convertLongToHex(self.__convertChannelMask(listChannelMask)) channelMask = '0x' + self.__convertLongToHex(self.__convertChannelMask(listChannelMask))
try: try:
cmd = 'commissioner announce %s %s %s %s' % ( cmd = 'commissioner announce %s %s %s %s' % (
@@ -2585,7 +2640,7 @@ class OpenThreadTHCI(object):
ModuleHelper.WriteIntoDebugLogger('MGMT_ANNOUNCE_BEGIN() error: ' + str(e)) ModuleHelper.WriteIntoDebugLogger('MGMT_ANNOUNCE_BEGIN() error: ' + str(e))
@API @API
def MGMT_ACTIVE_GET(self, Addr='', TLVs=[]): def MGMT_ACTIVE_GET(self, Addr='', TLVs=()):
"""send MGMT_ACTIVE_GET command """send MGMT_ACTIVE_GET command
Returns: Returns:
@@ -2770,7 +2825,7 @@ class OpenThreadTHCI(object):
ModuleHelper.WriteIntoDebugLogger('MGMT_ACTIVE_SET() Error: ' + str(e)) ModuleHelper.WriteIntoDebugLogger('MGMT_ACTIVE_SET() Error: ' + str(e))
@API @API
def MGMT_PENDING_GET(self, Addr='', TLVs=[]): def MGMT_PENDING_GET(self, Addr='', TLVs=()):
"""send MGMT_PENDING_GET command """send MGMT_PENDING_GET command
Returns: Returns:
@@ -2874,7 +2929,7 @@ class OpenThreadTHCI(object):
ModuleHelper.WriteIntoDebugLogger('MGMT_PENDING_SET() Error: ' + str(e)) ModuleHelper.WriteIntoDebugLogger('MGMT_PENDING_SET() Error: ' + str(e))
@API @API
def MGMT_COMM_GET(self, Addr='ff02::1', TLVs=[]): def MGMT_COMM_GET(self, Addr='ff02::1', TLVs=()):
"""send MGMT_COMM_GET command """send MGMT_COMM_GET command
Returns: Returns:
@@ -2949,7 +3004,7 @@ class OpenThreadTHCI(object):
ModuleHelper.WriteIntoDebugLogger('MGMT_COMM_SET() Error: ' + str(e)) ModuleHelper.WriteIntoDebugLogger('MGMT_COMM_SET() Error: ' + str(e))
@API @API
def setActiveDataset(self, listActiveDataset=[]): def setActiveDataset(self, listActiveDataset=()):
print('%s call setActiveDataset' % self) print('%s call setActiveDataset' % self)
@API @API
@@ -3457,8 +3512,8 @@ class OpenThreadTHCI(object):
"""Detect if the device is running Zephyr and adapt in that case""" """Detect if the device is running Zephyr and adapt in that case"""
try: try:
if self.__executeCommand('ot thread version')[0].isdigit(): if self.__executeCommand(ZEPHYR_PREFIX + 'thread version')[0].isdigit():
self.__cmdPrefix = 'ot ' self.__cmdPrefix = ZEPHYR_PREFIX
self.__lineSepX = re.compile(r'\r\n|\r|\n') self.__lineSepX = re.compile(r'\r\n|\r|\n')
except CommandError: except CommandError:
pass pass
@@ -3505,16 +3560,27 @@ class OpenThread(OpenThreadTHCI, IThci):
def _connect(self): def _connect(self):
print('My port is %s' % self) print('My port is %s' % self)
self.__lines = [] self.__lines = []
timeout = 10
port_error = None
if self.port.startswith('COM'): if self.port.startswith('COM'):
self.__handle = serial.Serial(self.port, 115200, timeout=0) for _ in range(int(timeout / 0.5)):
self.sleep(1) time.sleep(0.5)
self.__handle.write('\r\n') try:
self.sleep(0.1) self.__handle = serial.Serial(self.port, 115200, timeout=0)
self._is_net = False self.sleep(1)
self.__handle.write('\r\n')
self.sleep(0.1)
self._is_net = False
break
except SerialException as port_error:
self.log("{} port not ready, retrying to connect...".format(self.port))
else:
raise SerialException("Could not open {} port: {}".format(self.port, port_error))
elif ':' in self.port: elif ':' in self.port:
host, port = self.port.split(':') host, port = self.port.split(':')
self.__handle = socket.create_connection((host, port)) self.__handle = socket.create_connection((host, port))
self.__handle.setblocking(0) self.__handle.setblocking(False)
self._is_net = True self._is_net = True
else: else:
raise Exception('Unknown port schema') raise Exception('Unknown port schema')
@@ -3561,7 +3627,12 @@ class OpenThread(OpenThreadTHCI, IThci):
return self.__lines.pop(0) return self.__lines.pop(0)
def _cliWriteLine(self, line): def _cliWriteLine(self, line):
self.__socWrite(line + '\r\n') if self.__cmdPrefix == ZEPHYR_PREFIX:
if not line.startswith(self.__cmdPrefix):
line = self.__cmdPrefix + line
self.__socWrite(line + '\r')
else:
self.__socWrite(line + '\r\n')
def _onCommissionStart(self): def _onCommissionStart(self):
pass pass