IDFDUT.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. # Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http:#www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. """ DUT for IDF applications """
  15. import os
  16. import sys
  17. import re
  18. import subprocess
  19. import functools
  20. import serial
  21. from serial.tools import list_ports
  22. import DUT
  23. class IDFToolError(OSError):
  24. pass
  25. def _tool_method(func):
  26. """ close port, execute tool method and then reopen port """
  27. @functools.wraps(func)
  28. def handler(self, *args, **kwargs):
  29. self.close()
  30. ret = func(self, *args, **kwargs)
  31. self.open()
  32. return ret
  33. return handler
  34. class IDFDUT(DUT.SerialDUT):
  35. """ IDF DUT, extends serial with ESPTool methods """
  36. CHIP_TYPE_PATTERN = re.compile(r"Detecting chip type[.:\s]+(.+)")
  37. def __init__(self, name, port, log_file, app, **kwargs):
  38. self.download_config, self.partition_table = app.process_app_info()
  39. super(IDFDUT, self).__init__(name, port, log_file, app, **kwargs)
  40. @classmethod
  41. def get_chip(cls, app, port):
  42. """
  43. get chip id via esptool
  44. :param app: application instance (to get tool)
  45. :param port: comport
  46. :return: chip ID or None
  47. """
  48. try:
  49. output = subprocess.check_output(["python", app.esptool, "--port", port, "chip_id"])
  50. except subprocess.CalledProcessError:
  51. output = bytes()
  52. if isinstance(output, bytes):
  53. output = output.decode()
  54. chip_type = cls.CHIP_TYPE_PATTERN.search(output)
  55. return chip_type.group(1) if chip_type else None
  56. @classmethod
  57. def confirm_dut(cls, port, app, **kwargs):
  58. return cls.get_chip(app, port) is not None
  59. @_tool_method
  60. def start_app(self):
  61. """
  62. download and start app.
  63. :return: None
  64. """
  65. retry_baud_rates = ["921600", "115200"]
  66. error = IDFToolError()
  67. for baud_rate in retry_baud_rates:
  68. try:
  69. subprocess.check_output(["python", self.app.esptool,
  70. "--port", self.port, "--baud", baud_rate]
  71. + self.download_config)
  72. break
  73. except subprocess.CalledProcessError as error:
  74. continue
  75. else:
  76. raise error
  77. @_tool_method
  78. def reset(self):
  79. """
  80. reset DUT with esptool
  81. :return: None
  82. """
  83. subprocess.check_output(["python", self.app.esptool, "--port", self.port, "run"])
  84. @_tool_method
  85. def dump_flush(self, output_file, **kwargs):
  86. """
  87. dump flush
  88. :param output_file: output file name, if relative path, will use sdk path as base path.
  89. :keyword partition: partition name, dump the partition.
  90. ``partition`` is preferred than using ``address`` and ``size``.
  91. :keyword address: dump from address (need to be used with size)
  92. :keyword size: dump size (need to be used with address)
  93. :return: None
  94. """
  95. if os.path.isabs(output_file) is False:
  96. output_file = os.path.relpath(output_file, self.app.get_log_folder())
  97. if "partition" in kwargs:
  98. partition = self.partition_table[kwargs["partition"]]
  99. _address = partition["offset"]
  100. _size = partition["size"]
  101. elif "address" in kwargs and "size" in kwargs:
  102. _address = kwargs["address"]
  103. _size = kwargs["size"]
  104. else:
  105. raise IDFToolError("You must specify 'partition' or ('address' and 'size') to dump flash")
  106. subprocess.check_output(
  107. ["python", self.app.esptool, "--port", self.port, "--baud", "921600",
  108. "--before", "default_reset", "--after", "hard_reset", "read_flash",
  109. _address, _size, output_file]
  110. )
  111. @classmethod
  112. def list_available_ports(cls):
  113. ports = [x.device for x in list_ports.comports()]
  114. espport = os.getenv('ESPPORT')
  115. if not espport:
  116. return ports
  117. port_hint = espport.decode('utf8')
  118. # If $ESPPORT is a valid port, make it appear first in the list
  119. if port_hint in ports:
  120. ports.remove(port_hint)
  121. return [port_hint] + ports
  122. # On macOS, user may set ESPPORT to /dev/tty.xxx while
  123. # pySerial lists only the corresponding /dev/cu.xxx port
  124. if sys.platform == 'darwin' and 'tty.' in port_hint:
  125. port_hint = port_hint.replace('tty.', 'cu.')
  126. if port_hint in ports:
  127. ports.remove(port_hint)
  128. return [port_hint] + ports
  129. return ports