docs: def main(custom_commandline=None):
      """
      Main function for esptool
  
      custom_commandline - Optional override for default arguments parsing (that uses sys.argv), can be a list of custom arguments
      as strings. Arguments and their values need to be added as individual items to the list e.g. "-b 115200" thus
      becomes ['-b', '115200'].
      """
      parser = argparse.ArgumentParser(description='esptool.py v%s - ESP8266 ROM Bootloader Utility' % __version__, prog='esptool')
  
      parser.add_argument('--chip', '-c',
                          help='Target chip type',
                          choices=['auto', 'esp8266', 'esp32'],
                          default=os.environ.get('ESPTOOL_CHIP', 'auto'))
  
      parser.add_argument(
          '--port', '-p',
          help='Serial port device',
          default=os.environ.get('ESPTOOL_PORT', None))
  
      parser.add_argument(
          '--baud', '-b',
          help='Serial port baud rate used when flashing/reading',
          type=arg_auto_int,
          default=os.environ.get('ESPTOOL_BAUD', ESPLoader.ESP_ROM_BAUD))
  
      parser.add_argument(
          '--before',
          help='What to do before connecting to the chip',
          choices=['default_reset', 'no_reset', 'no_reset_no_sync'],
          default=os.environ.get('ESPTOOL_BEFORE', 'default_reset'))
  
      parser.add_argument(
          '--after', '-a',
          help='What to do after esptool.py is finished',
          choices=['hard_reset', 'soft_reset', 'no_reset'],
          default=os.environ.get('ESPTOOL_AFTER', 'hard_reset'))
  
      parser.add_argument(
          '--no-stub',
          help="Disable launching the flasher stub, only talk to ROM bootloader. Some features will not be available.",
          action='store_true')
  
      parser.add_argument(
          '--trace', '-t',
          help="Enable trace-level output of esptool.py interactions.",
          action='store_true')
  
      parser.add_argument(
          '--override-vddsdio',
          help="Override ESP32 VDDSDIO internal voltage regulator (use with care)",
          choices=ESP32ROM.OVERRIDE_VDDSDIO_CHOICES,
          nargs='?')
  
      subparsers = parser.add_subparsers(
          dest='operation',
          help='Run esptool {command} -h for additional help')
  
      def add_spi_connection_arg(parent):
          parent.add_argument('--spi-connection', '-sc', help='ESP32-only argument. Override default SPI Flash connection. ' +
                              'Value can be SPI, HSPI or a comma-separated list of 5 I/O numbers to use for SPI flash (CLK,Q,D,HD,CS).',
                              action=SpiConnectionAction)
  
      parser_load_ram = subparsers.add_parser(
          'load_ram',
          help='Download an image to RAM and execute')
      parser_load_ram.add_argument('filename', help='Firmware image')
  
      parser_dump_mem = subparsers.add_parser(
          'dump_mem',
          help='Dump arbitrary memory to disk')
      parser_dump_mem.add_argument('address', help='Base address', type=arg_auto_int)
      parser_dump_mem.add_argument('size', help='Size of region to dump', type=arg_auto_int)
      parser_dump_mem.add_argument('filename', help='Name of binary dump')
  
      parser_read_mem = subparsers.add_parser(
          'read_mem',
          help='Read arbitrary memory location')
      parser_read_mem.add_argument('address', help='Address to read', type=arg_auto_int)
  
      parser_write_mem = subparsers.add_parser(
          'write_mem',
          help='Read-modify-write to arbitrary memory location')
      parser_write_mem.add_argument('address', help='Address to write', type=arg_auto_int)
      parser_write_mem.add_argument('value', help='Value', type=arg_auto_int)
      parser_write_mem.add_argument('mask', help='Mask of bits to write', type=arg_auto_int)
  
      def add_spi_flash_subparsers(parent, is_elf2image):
          """ Add common parser arguments for SPI flash properties """
          extra_keep_args = [] if is_elf2image else ['keep']
          auto_detect = not is_elf2image
  
          if auto_detect:
              extra_fs_message = ", detect, or keep"
          else:
              extra_fs_message = ""
  
          parent.add_argument('--flash_freq', '-ff', help='SPI Flash frequency',
                              choices=extra_keep_args + ['40m', '26m', '20m', '80m'],
                              default=os.environ.get('ESPTOOL_FF', '40m' if is_elf2image else 'keep'))
          parent.add_argument('--flash_mode', '-fm', help='SPI Flash mode',
                              choices=extra_keep_args + ['qio', 'qout', 'dio', 'dout'],
                              default=os.environ.get('ESPTOOL_FM', 'qio' if is_elf2image else 'keep'))
          parent.add_argument('--flash_size', '-fs', help='SPI Flash size in MegaBytes (1MB, 2MB, 4MB, 8MB, 16M)'
                              ' plus ESP8266-only (256KB, 512KB, 2MB-c1, 4MB-c1)' + extra_fs_message,
                              action=FlashSizeAction, auto_detect=auto_detect,
                              default=os.environ.get('ESPTOOL_FS', 'detect' if auto_detect else '1MB'))
          add_spi_connection_arg(parent)
  
      parser_write_flash = subparsers.add_parser(
          'write_flash',
          help='Write a binary blob to flash')
  
      parser_write_flash.add_argument('addr_filename', metavar='<address> <filename>', help='Address followed by binary filename, separated by space',
                                      action=AddrFilenamePairAction)
      parser_write_flash.add_argument('--erase-all', '-e',
                                      help='Erase all regions of flash (not just write areas) before programming',
                                      action="store_true")
  
      add_spi_flash_subparsers(parser_write_flash, is_elf2image=False)
      parser_write_flash.add_argument('--no-progress', '-p', help='Suppress progress output', action="store_true")
      parser_write_flash.add_argument('--verify', help='Verify just-written data on flash ' +
                                      '(mostly superfluous, data is read back during flashing)', action='store_true')
      parser_write_flash.add_argument('--encrypt', help='Encrypt before write ',
                                      action='store_true')
      parser_write_flash.add_argument('--ignore-flash-encryption-efuse-setting', help='Ignore flash encryption efuse settings ',
                                      action='store_true')
  
      compress_args = parser_write_flash.add_mutually_exclusive_group(required=False)
      compress_args.add_argument('--compress', '-z', help='Compress data in transfer (default unless --no-stub is specified)',action="store_true", default=None)
      compress_args.add_argument('--no-compress', '-u', help='Disable data compression during transfer (default if --no-stub is specified)',action="store_true")
  
      subparsers.add_parser(
          'run',
          help='Run application code in flash')
  
      parser_image_info = subparsers.add_parser(
          'image_info',
          help='Dump headers from an application image')
      parser_image_info.add_argument('filename', help='Image file to parse')
  
      parser_make_image = subparsers.add_parser(
          'make_image',
          help='Create an application image from binary files')
      parser_make_image.add_argument('output', help='Output image file')
      parser_make_image.add_argument('--segfile', '-f', action='append', help='Segment input file')
      parser_make_image.add_argument('--segaddr', '-a', action='append', help='Segment base address', type=arg_auto_int)
      parser_make_image.add_argument('--entrypoint', '-e', help='Address of entry point', type=arg_auto_int, default=0)
  
      parser_elf2image = subparsers.add_parser(
          'elf2image',
          help='Create an application image from ELF file')
      parser_elf2image.add_argument('input', help='Input ELF file')
      parser_elf2image.add_argument('--output', '-o', help='Output filename prefix (for version 1 image), or filename (for version 2 single image)', type=str)
      parser_elf2image.add_argument('--version', '-e', help='Output image version', choices=['1','2'], default='1')
      parser_elf2image.add_argument('--secure-pad', action='store_true', help='Pad image so once signed it will end on a 64KB boundary. For ESP32 images only.')
      parser_elf2image.add_argument('--elf-sha256-offset', help='If set, insert SHA256 hash (32 bytes) of the input ELF file at specified offset in the binary.',
                                    type=arg_auto_int, default=None)
  
      add_spi_flash_subparsers(parser_elf2image, is_elf2image=True)
  
      subparsers.add_parser(
          'read_mac',
          help='Read MAC address from OTP ROM')
  
      subparsers.add_parser(
          'chip_id',
          help='Read Chip ID from OTP ROM')
  
      parser_flash_id = subparsers.add_parser(
          'flash_id',
          help='Read SPI flash manufacturer and device ID')
      add_spi_connection_arg(parser_flash_id)
  
      parser_read_status = subparsers.add_parser(
          'read_flash_status',
          help='Read SPI flash status register')
  
      add_spi_connection_arg(parser_read_status)
      parser_read_status.add_argument('--bytes', help='Number of bytes to read (1-3)', type=int, choices=[1,2,3], default=2)
  
      parser_write_status = subparsers.add_parser(
          'write_flash_status',
          help='Write SPI flash status register')
  
      add_spi_connection_arg(parser_write_status)
      parser_write_status.add_argument('--non-volatile', help='Write non-volatile bits (use with caution)', action='store_true')
      parser_write_status.add_argument('--bytes', help='Number of status bytes to write (1-3)', type=int, choices=[1,2,3], default=2)
      parser_write_status.add_argument('value', help='New value', type=arg_auto_int)
  
      parser_read_flash = subparsers.add_parser(
          'read_flash',
          help='Read SPI flash content')
      add_spi_connection_arg(parser_read_flash)
      parser_read_flash.add_argument('address', help='Start address', type=arg_auto_int)
      parser_read_flash.add_argument('size', help='Size of region to dump', type=arg_auto_int)
      parser_read_flash.add_argument('filename', help='Name of binary dump')
      parser_read_flash.add_argument('--no-progress', '-p', help='Suppress progress output', action="store_true")
  
      parser_verify_flash = subparsers.add_parser(
          'verify_flash',
          help='Verify a binary blob against flash')
      parser_verify_flash.add_argument('addr_filename', help='Address and binary file to verify there, separated by space',
                                       action=AddrFilenamePairAction)
      parser_verify_flash.add_argument('--diff', '-d', help='Show differences',
                                       choices=['no', 'yes'], default='no')
      add_spi_flash_subparsers(parser_verify_flash, is_elf2image=False)
  
      parser_erase_flash = subparsers.add_parser(
          'erase_flash',
          help='Perform Chip Erase on SPI flash')
      add_spi_connection_arg(parser_erase_flash)
  
      parser_erase_region = subparsers.add_parser(
          'erase_region',
          help='Erase a region of the flash')
      add_spi_connection_arg(parser_erase_region)
      parser_erase_region.add_argument('address', help='Start address (must be multiple of 4096)', type=arg_auto_int)
      parser_erase_region.add_argument('size', help='Size of region to erase (must be multiple of 4096)', type=arg_auto_int)
  
      subparsers.add_parser(
          'version', help='Print esptool version')
  
      # internal sanity check - every operation matches a module function of the same name
      for operation in subparsers.choices.keys():
          assert operation in globals(), "%s should be a module function" % operation
  
      expand_file_arguments()
  
      args = parser.parse_args(custom_commandline)
  
      print('esptool.py v%s' % __version__)
  
      # operation function can take 1 arg (args), 2 args (esp, arg)
      # or be a member function of the ESPLoader class.
  
      if args.operation is None:
          parser.print_help()
          sys.exit(1)
  
      operation_func = globals()[args.operation]
  
      if PYTHON2:
          # This function is depreciated in Python3
          operation_args = inspect.getargspec(operation_func).args
      else:
          operation_args = inspect.getfullargspec(operation_func).args
  
      if operation_args[0] == 'esp':  # operation function takes an ESPLoader connection object
          if args.before != "no_reset_no_sync":
              initial_baud = min(ESPLoader.ESP_ROM_BAUD, args.baud)  # don't sync faster than the default baud rate
          else:
              initial_baud = args.baud
  
          if args.port is None:
              ser_list = sorted(ports.device for ports in list_ports.comports())
              print("Found %d serial ports" % len(ser_list))
          else:
              ser_list = [args.port]
          esp = None
          for each_port in reversed(ser_list):
              print("Serial port %s" % each_port)
              try:
                  if args.chip == 'auto':
                      esp = ESPLoader.detect_chip(each_port, initial_baud, args.before, args.trace)
                  else:
                      chip_class = {
                          'esp8266': ESP8266ROM,
                          'esp32': ESP32ROM,
                      }[args.chip]
                      esp = chip_class(each_port, initial_baud, args.trace)
                      esp.connect(args.before)
                  break
              except (FatalError, OSError) as err:
                  if args.port is not None:
                      raise
                  print("%s failed to connect: %s" % (each_port, err))
                  esp = None
          if esp is None:
              raise FatalError("Could not connect to an Espressif device on any of the %d available serial ports." % len(ser_list))
  
          print("Chip is %s" % (esp.get_chip_description()))
  
          print("Features: %s" % ", ".join(esp.get_chip_features()))
  
          print("Crystal is %dMHz" % esp.get_crystal_freq())
  
          read_mac(esp, args)
  
          if not args.no_stub:
              esp = esp.run_stub()
  
          if args.override_vddsdio:
              esp.override_vddsdio(args.override_vddsdio)
  
          if args.baud > initial_baud:
              try:
                  esp.change_baud(args.baud)
              except NotImplementedInROMError:
                  print("WARNING: ROM doesn't support changing baud rate. Keeping initial baud rate %d" % initial_baud)
  
          # override common SPI flash parameter stuff if configured to do so
          if hasattr(args, "spi_connection") and args.spi_connection is not None:
              if esp.CHIP_NAME != "ESP32":
                  raise FatalError("Chip %s does not support --spi-connection option." % esp.CHIP_NAME)
              print("Configuring SPI flash mode...")
              esp.flash_spi_attach(args.spi_connection)
          elif args.no_stub:
              print("Enabling default SPI flash mode...")
              # ROM loader doesn't enable flash unless we explicitly do it
              esp.flash_spi_attach(0)
  
          if hasattr(args, "flash_size"):
              print("Configuring flash size...")
              detect_flash_size(esp, args)
              if args.flash_size != 'keep':  # TODO: should set this even with 'keep'
                  esp.flash_set_parameters(flash_size_bytes(args.flash_size))
  
          try:
              operation_func(esp, args)
          finally:
              try:  # Clean up AddrFilenamePairAction files
                  for address, argfile in args.addr_filename:
                      argfile.close()
              except AttributeError:
                  pass
  
          # Handle post-operation behaviour (reset or other)
          if operation_func == load_ram:
              # the ESP is now running the loaded image, so let it run
              print('Exiting immediately.')
          elif args.after == 'hard_reset':
              print('Hard resetting via RTS pin...')
              esp.hard_reset()
          elif args.after == 'soft_reset':
              print('Soft resetting...')
              # flash_finish will trigger a soft reset
              esp.soft_reset(False)
          else:
              print('Staying in bootloader.')
              if esp.IS_STUB:
                  esp.soft_reset(True)  # exit stub back to ROM loader
  
          esp._port.close()
  
      else:
          operation_func(args)