esp32_firmware_utils.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. #!/usr/bin/env python3
  2. # Copyright (c) 2020 Project CHIP Authors
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. """Flash an ESP32 device.
  16. This is layered so that a caller can perform individual operations
  17. through an `Flasher` instance, or operations according to a command line.
  18. For `Flasher`, see the class documentation. For the parse_command()
  19. interface or standalone execution:
  20. usage: esp32_firmware_utils.py [-h] [--verbose] [--erase] [--application FILE]
  21. [--verify_application] [--reset] [--skip_reset]
  22. [--esptool FILE] [--parttool FILE]
  23. [--sdkconfig FILE] [--chip CHIP] [--port PORT]
  24. [--baud BAUD] [--before ACTION]
  25. [--after ACTION] [--flash_mode MODE]
  26. [--flash_freq FREQ] [--flash_size SIZE]
  27. [--compress] [--bootloader FILE]
  28. [--bootloader_offset OFFSET] [--partition FILE]
  29. [--partition_offset OFFSET]
  30. [--application_offset OFFSET]
  31. Flash ESP32 device
  32. optional arguments:
  33. -h, --help show this help message and exit
  34. configuration:
  35. --verbose, -v Report more verbosely
  36. --esptool FILE File name of the esptool executable
  37. --parttool FILE File name of the parttool executable
  38. --sdkconfig FILE File containing option defaults
  39. --chip CHIP Target chip type
  40. --port PORT Serial port device
  41. --baud BAUD Serial port baud rate
  42. --before ACTION What to do before connecting
  43. --after ACTION What to do when finished
  44. --flash_mode MODE, --flash-mode MODE
  45. Flash mode
  46. --flash_freq FREQ, --flash-freq FREQ
  47. Flash frequency
  48. --flash_size SIZE, --flash-size SIZE
  49. Flash size
  50. --compress, -z Compress data in transfer
  51. --bootloader FILE Bootloader image
  52. --bootloader_offset OFFSET, --bootloader-offset OFFSET
  53. Bootloader offset
  54. --partition FILE Partition table image
  55. --partition_offset OFFSET, --partition-offset OFFSET
  56. Partition table offset
  57. --application_offset OFFSET, --application-offset OFFSET
  58. Application offset
  59. operations:
  60. --erase Erase device
  61. --application FILE Flash an image
  62. --verify_application, --verify-application
  63. Verify the image after flashing
  64. --reset Reset device after flashing
  65. --skip_reset, --skip-reset
  66. Do not reset device after flashing
  67. """
  68. import os
  69. import sys
  70. import firmware_utils
  71. # Additional options that can be use to configure an `Flasher`
  72. # object (as dictionary keys) and/or passed as command line options.
  73. ESP32_OPTIONS = {
  74. # Configuration options define properties used in flashing operations.
  75. 'configuration': {
  76. # Tool configuration options.
  77. 'esptool': {
  78. 'help': 'File name of the esptool executable',
  79. 'default': None,
  80. 'argparse': {
  81. 'metavar': 'FILE',
  82. },
  83. 'command': [
  84. {'option': 'esptool'},
  85. {'optional': 'port'},
  86. {'optional': 'baud'},
  87. {'optional': 'before'},
  88. {'optional': 'after'},
  89. ()
  90. ],
  91. 'verify': ['{esptool}', 'version'],
  92. 'error':
  93. """\
  94. Unable to execute {esptool}.
  95. Please ensure that this tool is installed and
  96. that $IDF_PATH is set. See the ESP32 example
  97. README for installation instructions.
  98. """,
  99. },
  100. 'parttool': {
  101. 'help': 'File name of the parttool executable',
  102. 'default': None,
  103. 'argparse': {
  104. 'metavar': 'FILE'
  105. },
  106. 'command': [
  107. {'option': 'parttool'},
  108. {'optional': 'port'},
  109. {'optional': 'baud'},
  110. {
  111. 'option': 'partition',
  112. 'result': ['--partition-table-file', '{partition}'],
  113. 'expand': True
  114. },
  115. ()
  116. ],
  117. 'verify': ['{parttool}', '--quiet'],
  118. 'error':
  119. """\
  120. Unable to execute {parttool}.
  121. Please ensure that this tool is installed and
  122. that $IDF_PATH is set. See the ESP32 example
  123. README for installation instructions.
  124. """,
  125. },
  126. 'sdkconfig': {
  127. 'help': 'File containing option defaults',
  128. 'default': None,
  129. 'argparse': {
  130. 'metavar': 'FILE'
  131. },
  132. },
  133. # Device configuration options.
  134. 'chip': {
  135. 'help': 'Target chip type',
  136. 'default': 'esp32',
  137. 'argparse': {
  138. 'metavar': 'CHIP'
  139. },
  140. 'sdkconfig': 'CONFIG_IDF_TARGET',
  141. },
  142. 'port': {
  143. 'help': 'Serial port device',
  144. 'default': None,
  145. 'argparse': {
  146. 'metavar': 'PORT',
  147. },
  148. 'sdkconfig': 'CONFIG_ESPTOOLPY_PORT',
  149. },
  150. 'baud': {
  151. 'help': 'Serial port baud rate',
  152. 'default': None,
  153. 'argparse': {
  154. 'metavar': 'BAUD',
  155. },
  156. 'sdkconfig': 'CONFIG_ESPTOOLPY_BAUD',
  157. },
  158. 'before': {
  159. 'help': 'What to do before connecting',
  160. 'default': None,
  161. 'argparse': {
  162. 'metavar': 'ACTION',
  163. },
  164. 'sdkconfig': 'CONFIG_ESPTOOLPY_BEFORE',
  165. },
  166. 'after': {
  167. 'help': 'What to do when finished',
  168. 'default': None,
  169. 'argparse': {
  170. 'metavar': 'ACTION',
  171. },
  172. 'sdkconfig': 'CONFIG_ESPTOOLPY_AFTER',
  173. },
  174. 'flash_mode': {
  175. 'help': 'Flash mode',
  176. 'default': None,
  177. 'argparse': {
  178. 'metavar': 'MODE',
  179. },
  180. 'sdkconfig': 'CONFIG_ESPTOOLPY_FLASHMODE',
  181. },
  182. 'flash_freq': {
  183. 'help': 'Flash frequency',
  184. 'default': None,
  185. 'argparse': {
  186. 'metavar': 'FREQ',
  187. },
  188. 'sdkconfig': 'CONFIG_ESPTOOLPY_FLASHFREQ',
  189. },
  190. 'flash_size': {
  191. 'help': 'Flash size',
  192. 'default': None,
  193. 'argparse': {
  194. 'metavar': 'SIZE',
  195. },
  196. 'sdkconfig': 'CONFIG_ESPTOOLPY_FLASHSIZE',
  197. },
  198. 'compress': {
  199. 'help': 'Compress data in transfer',
  200. 'default': None,
  201. 'alias': ['-z'],
  202. 'argparse': {
  203. 'action': 'store_true'
  204. },
  205. 'sdkconfig': 'CONFIG_ESPTOOLPY_COMPRESSED',
  206. },
  207. # Flashing things.
  208. 'bootloader': {
  209. 'help': 'Bootloader image',
  210. 'default': None,
  211. 'argparse': {
  212. 'metavar': 'FILE'
  213. },
  214. },
  215. 'bootloader_offset': {
  216. 'help': 'Bootloader offset',
  217. 'default': '0x1000',
  218. 'argparse': {
  219. 'metavar': 'OFFSET'
  220. },
  221. },
  222. 'partition': {
  223. 'help': 'Partition table image',
  224. 'default': None,
  225. 'argparse': {
  226. 'metavar': 'FILE'
  227. },
  228. },
  229. 'partition_offset': {
  230. 'help': 'Partition table offset',
  231. 'default': None,
  232. 'argparse': {
  233. 'metavar': 'OFFSET'
  234. },
  235. 'sdkconfig': 'CONFIG_PARTITION_TABLE_OFFSET',
  236. },
  237. 'application_offset': {
  238. 'help': 'Application offset',
  239. 'default': None,
  240. 'argparse': {
  241. 'metavar': 'OFFSET'
  242. },
  243. },
  244. },
  245. }
  246. def namespace_defaults(dst, src):
  247. for key, value in src.items():
  248. if key not in dst or getattr(dst, key) is None:
  249. setattr(dst, key, value)
  250. class Flasher(firmware_utils.Flasher):
  251. """Manage esp32 flashing."""
  252. def __init__(self, **options):
  253. super().__init__(platform='ESP32', module=__name__, **options)
  254. self.define_options(ESP32_OPTIONS)
  255. def _postprocess_argv(self):
  256. if self.option.sdkconfig:
  257. namespace_defaults(self.option,
  258. self.read_sdkconfig(self.option.sdkconfig))
  259. # idf = os.environ.get('IDF_PATH')
  260. # if idf:
  261. # if self.option.esptool is None:
  262. # self.option.esptool = os.path.join(
  263. # idf,
  264. # 'components/esptool_py/esptool/esptool.py')
  265. # if self.option.parttool is None:
  266. # self.option.parttool = os.path.join(
  267. # idf,
  268. # 'components/partition_table/parttool.py')
  269. def read_sdkconfig(self, filename):
  270. """Given an ESP32 sdkconfig file, read it for values of options
  271. not otherwise set.
  272. """
  273. config_map = {}
  274. for key, info in vars(self.info).items():
  275. config_key = info.get('sdkconfig')
  276. if config_key:
  277. config_map[config_key] = key
  278. result = {}
  279. with open(filename) as f:
  280. for line in f:
  281. k, eq, v = line.strip().partition('=')
  282. if eq == '=' and k in config_map:
  283. result[config_map[k]] = v.strip('"')
  284. return result
  285. IDF_PATH_TOOLS = {
  286. 'esptool': 'components/esptool_py/esptool/esptool.py',
  287. 'parttool': 'components/partition_table/parttool.py',
  288. }
  289. def locate_tool(self, tool):
  290. if tool in self.IDF_PATH_TOOLS:
  291. idf_path = os.environ.get('IDF_PATH')
  292. if idf_path:
  293. return os.path.join(idf_path, self.IDF_PATH_TOOLS[tool])
  294. return super().locate_tool(tool)
  295. # Common command line arguments for esptool flashing subcommands.
  296. FLASH_ARGUMENTS = [
  297. {'optional': 'flash_mode'},
  298. {'optional': 'flash_freq'},
  299. {'optional': 'flash_size'},
  300. {
  301. 'match': '{compress}',
  302. 'test': [(True, '-z'), ('y', '-z')],
  303. 'default': '-u'
  304. },
  305. ]
  306. def erase(self):
  307. """Perform `commander device masserase`."""
  308. return self.run_tool('esptool', ['erase_flash'], {}, 'Erase device')
  309. def verify(self, image, address=0):
  310. """Verify image(s)."""
  311. if not isinstance(image, list):
  312. image = [address, image]
  313. return self.run_tool(
  314. 'esptool',
  315. ['verify_flash', self.FLASH_ARGUMENTS, image],
  316. name='Verify',
  317. pass_message='Verified',
  318. fail_message='Not verified',
  319. fail_level=2)
  320. def flash(self, image, address=0):
  321. """Flash image(s)."""
  322. if not isinstance(image, list):
  323. image = [address, image]
  324. return self.run_tool(
  325. 'esptool',
  326. ['write_flash', self.FLASH_ARGUMENTS, image],
  327. name='Flash')
  328. PARTITION_INFO = {
  329. 'phy': ['--partition-type', 'data', '--partition-subtype', 'phy'],
  330. 'application': ['--partition-boot-default'],
  331. 'ota': ['--partition-type', 'data', '--partition-subtype', 'ota'],
  332. 'factory': [
  333. '--partition-type', 'app', '--partition-subtype', 'factory'
  334. ],
  335. }
  336. def get_partition_info(self, item, info, options=None):
  337. """Run parttool to get partition information."""
  338. return self.run_tool(
  339. 'parttool',
  340. ['get_partition_info', self.PARTITION_INFO[item], '--info', info],
  341. options or {},
  342. capture_output=True).stdout.strip()
  343. def make_wrapper(self, argv):
  344. self.parser.add_argument(
  345. '--use-parttool',
  346. metavar='FILENAME',
  347. help='partition tool to configure flashing script')
  348. self.parser.add_argument(
  349. '--use-partition-file',
  350. metavar='FILENAME',
  351. help='partition file to configure flashing script')
  352. self.parser.add_argument(
  353. '--use-sdkconfig',
  354. metavar='FILENAME',
  355. help='sdkconfig to configure flashing script')
  356. super().make_wrapper(argv)
  357. def _platform_wrapper_args(self, args):
  358. if args.use_sdkconfig:
  359. # Include values from sdkconfig so that it isn't needed at
  360. # flashing time.
  361. namespace_defaults(args, self.read_sdkconfig(args.use_sdkconfig))
  362. parttool = args.use_parttool or args.parttool
  363. partfile = args.use_partition_file or args.partition
  364. if parttool and partfile:
  365. # Get unspecified offsets from the partition file now,
  366. # so that parttool isn't needed at flashing time.
  367. if args.application and args.application_offset is None:
  368. args.application_offset = self.get_partition_info(
  369. 'application', 'offset',
  370. {'parttool': parttool, 'partition': partfile})
  371. def actions(self):
  372. """Perform actions on the device according to self.option."""
  373. self.log(3, 'Options:', self.option)
  374. if self.option.erase:
  375. if self.erase().err:
  376. return self
  377. bootloader = self.optional_file(self.option.bootloader)
  378. application = self.optional_file(self.option.application)
  379. partition = self.optional_file(self.option.partition)
  380. if bootloader or application or partition:
  381. # Collect the flashable items.
  382. flash = []
  383. if bootloader:
  384. flash += [self.option.bootloader_offset, bootloader]
  385. if application:
  386. offset = self.option.application_offset
  387. if offset is None:
  388. offset = self.get_partition_info('application', 'offset')
  389. flash += [offset, application]
  390. if partition:
  391. flash += [self.option.partition_offset, partition]
  392. # esptool.py doesn't have an independent reset command, so we add
  393. # an `--after` option to the final operation, which may be either
  394. # flash or verify.
  395. if self.option.after is None:
  396. if self.option.reset or self.option.reset is None:
  397. self.option.after = 'hard_reset'
  398. else:
  399. self.option.after = 'no_reset'
  400. if self.option.verify_application:
  401. verify_after = self.option.after
  402. self.option.after = 'no_reset'
  403. if self.flash(flash).err:
  404. return self
  405. if self.option.verify_application:
  406. self.option.after = verify_after
  407. if self.verify(flash).err:
  408. return self
  409. return self
  410. ### Mobly integration
  411. class ESP32Platform:
  412. def __init__(self, flasher_args):
  413. self.flasher = Flasher(**flasher_args)
  414. def flash(self):
  415. self.flasher.flash_command([os.getcwd()])
  416. def verify_platform_args(platform_args):
  417. required_args = [
  418. 'application',
  419. 'parttool',
  420. 'port',
  421. 'baud',
  422. 'before',
  423. 'after',
  424. 'flash_mode',
  425. 'flash_freq',
  426. 'flash_size',
  427. 'compress',
  428. 'bootloader',
  429. 'partition',
  430. 'partition_offset',
  431. 'application_offset',
  432. ]
  433. difference = set(required_args) - set(platform_args)
  434. if difference:
  435. raise ValueError("Required arguments missing: %s" % difference)
  436. def create_platform(platform_args):
  437. verify_platform_args(platform_args[0])
  438. return ESP32Platform(platform_args[0])
  439. ### End of Mobly integration
  440. if __name__ == '__main__':
  441. sys.exit(Flasher().flash_command(sys.argv))