Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow CLI to flash .uf2 files #19462

Merged
merged 1 commit into from
Jan 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 25 additions & 21 deletions lib/python/qmk/cli/flash.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@
from qmk.decorators import automagic_keyboard, automagic_keymap
from qmk.commands import compile_configurator_json, create_make_command, parse_configurator_json, build_environment
from qmk.keyboard import keyboard_completer, keyboard_folder
from qmk.keymap import keymap_completer
from qmk.flashers import flasher


def print_bootloader_help():
def _list_bootloaders():
"""Prints the available bootloaders listed in docs.qmk.fm.
"""
cli.print_help()
cli.log.info('Here are the available bootloaders:')
cli.echo('\tavrdude')
cli.echo('\tbootloadhid')
Expand All @@ -36,14 +38,29 @@ def print_bootloader_help():
cli.echo('\tuf2-split-left')
cli.echo('\tuf2-split-right')
cli.echo('For more info, visit https://docs.qmk.fm/#/flashing')
return False


def _flash_binary(filename, mcu):
"""Try to flash binary firmware
"""
cli.echo('Flashing binary firmware...\nPlease reset your keyboard into bootloader mode now!\nPress Ctrl-C to exit.\n')
try:
err, msg = flasher(mcu, filename)
if err:
cli.log.error(msg)
return False
except KeyboardInterrupt:
cli.log.info('Ctrl-C was pressed, exiting...')
return True


@cli.argument('filename', nargs='?', arg_only=True, type=qmk.path.FileType('r'), completer=FilesCompleter('.json'), help='A configurator export JSON to be compiled and flashed or a pre-compiled binary firmware file (bin/hex) to be flashed.')
@cli.argument('-b', '--bootloaders', action='store_true', help='List the available bootloaders.')
@cli.argument('-bl', '--bootloader', default='flash', help='The flash command, corresponding to qmk\'s make options of bootloaders.')
@cli.argument('-m', '--mcu', help='The MCU name. Required for HalfKay, HID, USBAspLoader and ISP flashing.')
@cli.argument('-km', '--keymap', help='The keymap to build a firmware for. Use this if you dont have a configurator file. Ignored when a configurator file is supplied.')
@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='The keyboard to build a firmware for. Use this if you dont have a configurator file. Ignored when a configurator file is supplied.')
@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='The keyboard to build a firmware for. Ignored when a configurator export is supplied.')
@cli.argument('-km', '--keymap', completer=keymap_completer, help='The keymap to build a firmware for. Ignored when a configurator export is supplied.')
@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Don't actually build, just show the make command to be run.")
@cli.argument('-j', '--parallel', type=int, default=1, help="Set the number of parallel make jobs; 0 means unlimited.")
@cli.argument('-e', '--env', arg_only=True, action='append', default=[], help="Set a variable to be passed to make. May be passed multiple times.")
Expand All @@ -56,30 +73,17 @@ def flash(cli):

If a binary firmware is supplied, try to flash that.

If a Configurator JSON export is supplied this command will create a new keymap. Keymap and Keyboard arguments
will be ignored.
If a Configurator export is supplied this command will create a new keymap, overwriting an existing keymap if one exists.

If no file is supplied, keymap and keyboard are expected.
If a keyboard and keymap are provided this command will build a firmware based on that.

If bootloader is omitted the make system will use the configured bootloader for that keyboard.
"""
if cli.args.filename and cli.args.filename.suffix in ['.bin', '.hex']:
# Try to flash binary firmware
cli.echo('Flashing binary firmware...\nPlease reset your keyboard into bootloader mode now!\nPress Ctrl-C to exit.\n')
try:
err, msg = flasher(cli.args.mcu, cli.args.filename)
if err:
cli.log.error(msg)
return False
except KeyboardInterrupt:
cli.log.info('Ctrl-C was pressed, exiting...')
return True
if cli.args.filename and cli.args.filename.suffix in ['.bin', '.hex', '.uf2']:
return _flash_binary(cli.args.filename, cli.args.mcu)

if cli.args.bootloaders:
# Provide usage and list bootloaders
cli.print_help()
print_bootloader_help()
return False
return _list_bootloaders()

# Build the environment vars
envs = build_environment(cli.args.env)
Expand Down
14 changes: 14 additions & 0 deletions lib/python/qmk/flashers.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ def _find_usb_device(vid_hex, pid_hex):
return usb.core.find(idVendor=vid_hex, idProduct=pid_hex)


def _find_uf2_devices():
"""Delegate to uf2conv.py as VID:PID pairs can potentially fluctuate more than other bootloaders
"""
return cli.run(['util/uf2conv.py', '--list']).stdout.splitlines()


def _find_bootloader():
# To avoid running forever in the background, only look for bootloaders for 10min
start_time = time.time()
Expand All @@ -95,6 +101,8 @@ def _find_bootloader():
else:
details = None
return (bl, details)
if _find_uf2_devices():
return ('_uf2_compatible_', None)
time.sleep(0.1)
return (None, None)

Expand Down Expand Up @@ -184,6 +192,10 @@ def _flash_mdloader(file):
cli.run(['mdloader', '--first', '--download', file, '--restart'], capture_output=False)


def _flash_uf2(file):
cli.run(['util/uf2conv.py', '--deploy', file], capture_output=False)


def flasher(mcu, file):
bl, details = _find_bootloader()
# Add a small sleep to avoid race conditions
Expand All @@ -208,6 +220,8 @@ def flasher(mcu, file):
return (True, "Specifying the MCU with '-m' is necessary for ISP flashing!")
elif bl == 'md-boot':
_flash_mdloader(file)
elif bl == '_uf2_compatible_':
_flash_uf2(file)
else:
return (True, "Known bootloader found but flashing not currently supported!")

Expand Down