summaryrefslogtreecommitdiffstats
path: root/lib/python/qmk
diff options
context:
space:
mode:
authorNick Brassel <nick@tzarc.org>2022-08-28 14:23:01 +1000
committerNick Brassel <nick@tzarc.org>2022-08-28 14:23:01 +1000
commit0a3f7e48690bb2b7b008300a54554979a55be19a (patch)
tree7499d52f20040ed7d5a56496ecb81ed114f80719 /lib/python/qmk
parentfc0bf67f372c38f72c303cdec21b1d4afb5e8cb4 (diff)
parent9b5b0722555891ba94f240760ef3a6d4c870fd13 (diff)
Merge remote-tracking branch 'upstream/develop'
Diffstat (limited to 'lib/python/qmk')
-rw-r--r--lib/python/qmk/c_parse.py3
-rw-r--r--lib/python/qmk/cli/__init__.py4
-rw-r--r--lib/python/qmk/cli/doctor/linux.py57
-rw-r--r--lib/python/qmk/cli/doctor/macos.py2
-rw-r--r--lib/python/qmk/cli/flash.py122
-rwxr-xr-xlib/python/qmk/cli/generate/api.py37
-rwxr-xr-xlib/python/qmk/cli/generate/config_h.py36
-rw-r--r--lib/python/qmk/cli/generate/dfu_header.py4
-rwxr-xr-xlib/python/qmk/cli/generate/info_json.py8
-rw-r--r--lib/python/qmk/cli/import/__init__.py0
-rw-r--r--lib/python/qmk/cli/import/kbfirmware.py25
-rw-r--r--lib/python/qmk/cli/import/keyboard.py23
-rw-r--r--lib/python/qmk/cli/import/keymap.py23
-rw-r--r--lib/python/qmk/constants.py56
-rw-r--r--lib/python/qmk/flashers.py203
-rw-r--r--lib/python/qmk/git.py4
-rw-r--r--lib/python/qmk/importers.py193
-rw-r--r--lib/python/qmk/info.py86
-rw-r--r--lib/python/qmk/json_schema.py6
19 files changed, 748 insertions, 144 deletions
diff --git a/lib/python/qmk/c_parse.py b/lib/python/qmk/c_parse.py
index 4b49b8d4e9..c14eb490fa 100644
--- a/lib/python/qmk/c_parse.py
+++ b/lib/python/qmk/c_parse.py
@@ -258,6 +258,9 @@ def _parse_led_config(file, matrix_cols, matrix_rows):
position_raw.append(_coerce_led_token(_type, value))
if section == 3 and bracket_count == 2:
flags.append(_coerce_led_token(_type, value))
+ elif _type in [Token.Comment.Preproc]:
+ # TODO: Promote to error
+ return None
# Slightly better intrim format
matrix = list(_get_chunks(matrix_raw, matrix_cols))
diff --git a/lib/python/qmk/cli/__init__.py b/lib/python/qmk/cli/__init__.py
index 1e15c28758..98e212c47b 100644
--- a/lib/python/qmk/cli/__init__.py
+++ b/lib/python/qmk/cli/__init__.py
@@ -15,6 +15,7 @@ from milc.questions import yesno
import_names = {
# A mapping of package name to importable name
'pep8-naming': 'pep8ext_naming',
+ 'pyserial': 'serial',
'pyusb': 'usb.core',
'qmk-dotty-dict': 'dotty_dict',
'pillow': 'PIL'
@@ -59,6 +60,9 @@ subcommands = [
'qmk.cli.generate.rules_mk',
'qmk.cli.generate.version_h',
'qmk.cli.hello',
+ 'qmk.cli.import.kbfirmware',
+ 'qmk.cli.import.keyboard',
+ 'qmk.cli.import.keymap',
'qmk.cli.info',
'qmk.cli.json2c',
'qmk.cli.lint',
diff --git a/lib/python/qmk/cli/doctor/linux.py b/lib/python/qmk/cli/doctor/linux.py
index 94683d3307..a803305c0d 100644
--- a/lib/python/qmk/cli/doctor/linux.py
+++ b/lib/python/qmk/cli/doctor/linux.py
@@ -6,7 +6,7 @@ from pathlib import Path
from milc import cli
-from qmk.constants import QMK_FIRMWARE
+from qmk.constants import QMK_FIRMWARE, BOOTLOADER_VIDS_PIDS
from .check import CheckStatus
@@ -26,6 +26,18 @@ def _udev_rule(vid, pid=None, *args):
return rule
+def _generate_desired_rules(bootloader_vids_pids):
+ rules = dict()
+ for bl in bootloader_vids_pids.keys():
+ rules[bl] = set()
+ for vid_pid in bootloader_vids_pids[bl]:
+ if bl == 'caterina' or bl == 'md-boot':
+ rules[bl].add(_udev_rule(vid_pid[0], vid_pid[1], 'ENV{ID_MM_DEVICE_IGNORE}="1"'))
+ else:
+ rules[bl].add(_udev_rule(vid_pid[0], vid_pid[1]))
+ return rules
+
+
def _deprecated_udev_rule(vid, pid=None):
""" Helper function that return udev rules
@@ -47,47 +59,8 @@ def check_udev_rules():
Path("/run/udev/rules.d/"),
Path("/etc/udev/rules.d/"),
]
- desired_rules = {
- 'atmel-dfu': {
- _udev_rule("03eb", "2fef"), # ATmega16U2
- _udev_rule("03eb", "2ff0"), # ATmega32U2
- _udev_rule("03eb", "2ff3"), # ATmega16U4
- _udev_rule("03eb", "2ff4"), # ATmega32U4
- _udev_rule("03eb", "2ff9"), # AT90USB64
- _udev_rule("03eb", "2ffa"), # AT90USB162
- _udev_rule("03eb", "2ffb") # AT90USB128
- },
- 'kiibohd': {_udev_rule("1c11", "b007")},
- 'stm32': {
- _udev_rule("1eaf", "0003"), # STM32duino
- _udev_rule("0483", "df11") # STM32 DFU
- },
- 'bootloadhid': {_udev_rule("16c0", "05df")},
- 'usbasploader': {_udev_rule("16c0", "05dc")},
- 'massdrop': {_udev_rule("03eb", "6124", 'ENV{ID_MM_DEVICE_IGNORE}="1"')},
- 'caterina': {
- # Spark Fun Electronics
- _udev_rule("1b4f", "9203", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Pro Micro 3V3/8MHz
- _udev_rule("1b4f", "9205", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Pro Micro 5V/16MHz
- _udev_rule("1b4f", "9207", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # LilyPad 3V3/8MHz (and some Pro Micro clones)
- # Pololu Electronics
- _udev_rule("1ffb", "0101", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # A-Star 32U4
- # Arduino SA
- _udev_rule("2341", "0036", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Leonardo
- _udev_rule("2341", "0037", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Micro
- # Adafruit Industries LLC
- _udev_rule("239a", "000c", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Feather 32U4
- _udev_rule("239a", "000d", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # ItsyBitsy 32U4 3V3/8MHz
- _udev_rule("239a", "000e", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # ItsyBitsy 32U4 5V/16MHz
- # dog hunter AG
- _udev_rule("2a03", "0036", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Leonardo
- _udev_rule("2a03", "0037", 'ENV{ID_MM_DEVICE_IGNORE}="1"') # Micro
- },
- 'hid-bootloader': {
- _udev_rule("03eb", "2067"), # QMK HID
- _udev_rule("16c0", "0478") # PJRC halfkay
- }
- }
+
+ desired_rules = _generate_desired_rules(BOOTLOADER_VIDS_PIDS)
# These rules are no longer recommended, only use them to check for their presence.
deprecated_rules = {
diff --git a/lib/python/qmk/cli/doctor/macos.py b/lib/python/qmk/cli/doctor/macos.py
index 00fb272858..5d088c9492 100644
--- a/lib/python/qmk/cli/doctor/macos.py
+++ b/lib/python/qmk/cli/doctor/macos.py
@@ -8,6 +8,6 @@ from .check import CheckStatus
def os_test_macos():
"""Run the Mac specific tests.
"""
- cli.log.info("Detected {fg_cyan}macOS %s{fg_reset}.", platform.mac_ver()[0])
+ cli.log.info("Detected {fg_cyan}macOS %s (%s){fg_reset}.", platform.mac_ver()[0], 'Apple Silicon' if platform.processor() == 'arm' else 'Intel')
return CheckStatus.OK
diff --git a/lib/python/qmk/cli/flash.py b/lib/python/qmk/cli/flash.py
index 216896b974..c39f4b36d4 100644
--- a/lib/python/qmk/cli/flash.py
+++ b/lib/python/qmk/cli/flash.py
@@ -4,6 +4,7 @@ You can compile a keymap already in the repo or using a QMK Configurator export.
A bootloader must be specified.
"""
from subprocess import DEVNULL
+import sys
from argcomplete.completers import FilesCompleter
from milc import cli
@@ -12,6 +13,7 @@ import qmk.path
from qmk.decorators import automagic_keyboard, automagic_keymap
from qmk.commands import compile_configurator_json, create_make_command, parse_configurator_json
from qmk.keyboard import keyboard_completer, keyboard_folder
+from qmk.flashers import flasher
def print_bootloader_help():
@@ -33,12 +35,15 @@ def print_bootloader_help():
cli.echo('\tdfu-split-right')
cli.echo('\tdfu-util-split-left')
cli.echo('\tdfu-util-split-right')
+ cli.echo('\tuf2-split-left')
+ cli.echo('\tuf2-split-right')
cli.echo('For more info, visit https://docs.qmk.fm/#/flashing')
-@cli.argument('filename', nargs='?', arg_only=True, type=qmk.path.FileType('r'), completer=FilesCompleter('.json'), help='The configurator export JSON to compile.')
+@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('-n', '--dry-run', arg_only=True, action='store_true', help="Don't actually build, just show the make command to be run.")
@@ -51,6 +56,8 @@ def print_bootloader_help():
def flash(cli):
"""Compile and or flash QMK Firmware or keyboard/layout
+ 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.
@@ -58,56 +65,69 @@ def flash(cli):
If bootloader is omitted the make system will use the configured bootloader for that keyboard.
"""
- if cli.args.clean and not cli.args.filename and not cli.args.dry_run:
- if cli.config.flash.keyboard and cli.config.flash.keymap:
- command = create_make_command(cli.config.flash.keyboard, cli.config.flash.keymap, 'clean')
- cli.run(command, capture_output=False, stdin=DEVNULL)
-
- # Build the environment vars
- envs = {}
- for env in cli.args.env:
- if '=' in env:
- key, value = env.split('=', 1)
- envs[key] = value
- else:
- cli.log.warning('Invalid environment variable: %s', env)
-
- # Determine the compile command
- command = ''
-
- if cli.args.bootloaders:
- # Provide usage and list bootloaders
- cli.echo('usage: qmk flash [-h] [-b] [-n] [-kb KEYBOARD] [-km KEYMAP] [-bl BOOTLOADER] [filename]')
- print_bootloader_help()
- return False
-
- if cli.args.filename:
- # Handle compiling a configurator JSON
- user_keymap = parse_configurator_json(cli.args.filename)
- keymap_path = qmk.path.keymap(user_keymap['keyboard'])
- command = compile_configurator_json(user_keymap, cli.args.bootloader, parallel=cli.config.flash.parallel, **envs)
-
- cli.log.info('Wrote keymap to {fg_cyan}%s/%s/keymap.c', keymap_path, user_keymap['keymap'])
+ 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...')
+ sys.exit(0)
else:
- if cli.config.flash.keyboard and cli.config.flash.keymap:
- # Generate the make command for a specific keyboard/keymap.
- command = create_make_command(cli.config.flash.keyboard, cli.config.flash.keymap, cli.args.bootloader, parallel=cli.config.flash.parallel, **envs)
-
- elif not cli.config.flash.keyboard:
- cli.log.error('Could not determine keyboard!')
- elif not cli.config.flash.keymap:
- cli.log.error('Could not determine keymap!')
-
- # Compile the firmware, if we're able to
- if command:
- cli.log.info('Compiling keymap with {fg_cyan}%s', ' '.join(command))
- if not cli.args.dry_run:
- cli.echo('\n')
- compile = cli.run(command, capture_output=False, stdin=DEVNULL)
- return compile.returncode
+ if cli.args.clean and not cli.args.filename and not cli.args.dry_run:
+ if cli.config.flash.keyboard and cli.config.flash.keymap:
+ command = create_make_command(cli.config.flash.keyboard, cli.config.flash.keymap, 'clean')
+ cli.run(command, capture_output=False, stdin=DEVNULL)
+
+ # Build the environment vars
+ envs = {}
+ for env in cli.args.env:
+ if '=' in env:
+ key, value = env.split('=', 1)
+ envs[key] = value
+ else:
+ cli.log.warning('Invalid environment variable: %s', env)
+
+ # Determine the compile command
+ command = ''
+
+ if cli.args.bootloaders:
+ # Provide usage and list bootloaders
+ cli.echo('usage: qmk flash [-h] [-b] [-n] [-kb KEYBOARD] [-km KEYMAP] [-bl BOOTLOADER] [filename]')
+ print_bootloader_help()
+ return False
+
+ if cli.args.filename:
+ # Handle compiling a configurator JSON
+ user_keymap = parse_configurator_json(cli.args.filename)
+ keymap_path = qmk.path.keymap(user_keymap['keyboard'])
+ command = compile_configurator_json(user_keymap, cli.args.bootloader, parallel=cli.config.flash.parallel, **envs)
+
+ cli.log.info('Wrote keymap to {fg_cyan}%s/%s/keymap.c', keymap_path, user_keymap['keymap'])
- else:
- cli.log.error('You must supply a configurator export, both `--keyboard` and `--keymap`, or be in a directory for a keyboard or keymap.')
- cli.echo('usage: qmk flash [-h] [-b] [-n] [-kb KEYBOARD] [-km KEYMAP] [-bl BOOTLOADER] [filename]')
- return False
+ else:
+ if cli.config.flash.keyboard and cli.config.flash.keymap:
+ # Generate the make command for a specific keyboard/keymap.
+ command = create_make_command(cli.config.flash.keyboard, cli.config.flash.keymap, cli.args.bootloader, parallel=cli.config.flash.parallel, **envs)
+
+ elif not cli.config.flash.keyboard:
+ cli.log.error('Could not determine keyboard!')
+ elif not cli.config.flash.keymap:
+ cli.log.error('Could not determine keymap!')
+
+ # Compile the firmware, if we're able to
+ if command:
+ cli.log.info('Compiling keymap with {fg_cyan}%s', ' '.join(command))
+ if not cli.args.dry_run:
+ cli.echo('\n')
+ compile = cli.run(command, capture_output=False, stdin=DEVNULL)
+ return compile.returncode
+
+ else:
+ cli.log.error('You must supply a configurator export, both `--keyboard` and `--keymap`, or be in a directory for a keyboard or keymap.')
+ cli.echo('usage: qmk flash [-h] [-b] [-n] [-kb KEYBOARD] [-km KEYMAP] [-bl BOOTLOADER] [filename]')
+ return False
diff --git a/lib/python/qmk/cli/generate/api.py b/lib/python/qmk/cli/generate/api.py
index 0596b3f22b..8d8ca3cd41 100755
--- a/lib/python/qmk/cli/generate/api.py
+++ b/lib/python/qmk/cli/generate/api.py
@@ -12,21 +12,30 @@ from qmk.json_encoders import InfoJSONEncoder
from qmk.json_schema import json_load
from qmk.keyboard import find_readme, list_keyboards
-TEMPLATE_PATH = Path('data/templates/api/')
+DATA_PATH = Path('data')
+TEMPLATE_PATH = DATA_PATH / 'templates/api/'
BUILD_API_PATH = Path('.build/api_data/')
+def _filtered_keyboard_list():
+ """Perform basic filtering of list_keyboards
+ """
+ keyboard_list = list_keyboards()
+ if cli.args.filter:
+ kb_list = []
+ for keyboard_name in keyboard_list:
+ if any(i in keyboard_name for i in cli.args.filter):
+ kb_list.append(keyboard_name)
+ keyboard_list = kb_list
+ return keyboard_list
+
+
@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Don't write the data to disk.")
@cli.argument('-f', '--filter', arg_only=True, action='append', default=[], help="Filter the list of keyboards based on partial name matches the supplied value. May be passed multiple times.")
-@cli.subcommand('Creates a new keymap for the keyboard of your choosing', hidden=False if cli.config.user.developer else True)
+@cli.subcommand('Generate QMK API data', hidden=False if cli.config.user.developer else True)
def generate_api(cli):
"""Generates the QMK API data.
"""
- if BUILD_API_PATH.exists():
- shutil.rmtree(BUILD_API_PATH)
-
- shutil.copytree(TEMPLATE_PATH, BUILD_API_PATH)
-
v1_dir = BUILD_API_PATH / 'v1'
keyboard_all_file = v1_dir / 'keyboards.json' # A massive JSON containing everything
keyboard_list_file = v1_dir / 'keyboard_list.json' # A simple list of keyboard targets
@@ -34,14 +43,14 @@ def generate_api(cli):
keyboard_metadata_file = v1_dir / 'keyboard_metadata.json' # All the data configurator/via needs for initialization
usb_file = v1_dir / 'usb.json' # A mapping of USB VID/PID -> keyboard target
+ if BUILD_API_PATH.exists():
+ shutil.rmtree(BUILD_API_PATH)
+
+ shutil.copytree(TEMPLATE_PATH, BUILD_API_PATH)
+ shutil.copytree(DATA_PATH, v1_dir)
+
# Filter down when required
- keyboard_list = list_keyboards()
- if cli.args.filter:
- kb_list = []
- for keyboard_name in keyboard_list:
- if any(i in keyboard_name for i in cli.args.filter):
- kb_list.append(keyboard_name)
- keyboard_list = kb_list
+ keyboard_list = _filtered_keyboard_list()
kb_all = {}
usb_list = {}
diff --git a/lib/python/qmk/cli/generate/config_h.py b/lib/python/qmk/cli/generate/config_h.py
index 893892c479..a26dcdf7d7 100755
--- a/lib/python/qmk/cli/generate/config_h.py
+++ b/lib/python/qmk/cli/generate/config_h.py
@@ -134,6 +134,36 @@ def generate_config_items(kb_info_json, config_h_lines):
config_h_lines.append(f'#endif // {config_key}')
+def generate_encoder_config(encoder_json, config_h_lines, postfix=''):
+ """Generate the config.h lines for encoders."""
+ a_pads = []
+ b_pads = []
+ resolutions = []
+ for encoder in encoder_json.get("rotary", []):
+ a_pads.append(encoder["pin_a"])
+ b_pads.append(encoder["pin_b"])
+ resolutions.append(encoder.get("resolution", None))
+
+ config_h_lines.append(f'#ifndef ENCODERS_PAD_A{postfix}')
+ config_h_lines.append(f'# define ENCODERS_PAD_A{postfix} {{ { ", ".join(a_pads) } }}')
+ config_h_lines.append(f'#endif // ENCODERS_PAD_A{postfix}')
+
+ config_h_lines.append(f'#ifndef ENCODERS_PAD_B{postfix}')
+ config_h_lines.append(f'# define ENCODERS_PAD_B{postfix} {{ { ", ".join(b_pads) } }}')
+ config_h_lines.append(f'#endif // ENCODERS_PAD_B{postfix}')
+
+ if None in resolutions:
+ cli.log.debug("Unable to generate ENCODER_RESOLUTION configuration")
+ elif len(set(resolutions)) == 1:
+ config_h_lines.append(f'#ifndef ENCODER_RESOLUTION{postfix}')
+ config_h_lines.append(f'# define ENCODER_RESOLUTION{postfix} { resolutions[0] }')
+ config_h_lines.append(f'#endif // ENCODER_RESOLUTION{postfix}')
+ else:
+ config_h_lines.append(f'#ifndef ENCODER_RESOLUTIONS{postfix}')
+ config_h_lines.append(f'# define ENCODER_RESOLUTIONS{postfix} {{ { ", ".join(map(str,resolutions)) } }}')
+ config_h_lines.append(f'#endif // ENCODER_RESOLUTIONS{postfix}')
+
+
def generate_split_config(kb_info_json, config_h_lines):
"""Generate the config.h lines for split boards."""
if 'primary' in kb_info_json['split']:
@@ -173,6 +203,9 @@ def generate_split_config(kb_info_json, config_h_lines):
if 'right' in kb_info_json['split'].get('matrix_pins', {}):
config_h_lines.append(matrix_pins(kb_info_json['split']['matrix_pins']['right'], '_RIGHT'))
+ if 'right' in kb_info_json['split'].get('encoder', {}):
+ generate_encoder_config(kb_info_json['split']['encoder']['right'], config_h_lines, '_RIGHT')
+
@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to')
@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
@@ -198,6 +231,9 @@ def generate_config_h(cli):
if 'matrix_pins' in kb_info_json:
config_h_lines.append(matrix_pins(kb_info_json['matrix_pins']))
+ if 'encoder' in kb_info_json:
+ generate_encoder_config(kb_info_json['encoder'], config_h_lines)
+
if 'split' in kb_info_json:
generate_split_config(kb_info_json, config_h_lines)
diff --git a/lib/python/qmk/cli/generate/dfu_header.py b/lib/python/qmk/cli/generate/dfu_header.py
index e873117387..aa0252ca86 100644
--- a/lib/python/qmk/cli/generate/dfu_header.py
+++ b/lib/python/qmk/cli/generate/dfu_header.py
@@ -33,8 +33,8 @@ def generate_dfu_header(cli):
kb_info_json = dotty(info_json(cli.config.generate_dfu_header.keyboard))
keyboard_h_lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#pragma once']
- keyboard_h_lines.append(f'#define MANUFACTURER {kb_info_json["manufacturer"]}')
- keyboard_h_lines.append(f'#define PRODUCT {kb_info_json["keyboard_name"]} Bootloader')
+ keyboard_h_lines.append(f'#define MANUFACTURER "{kb_info_json["manufacturer"]}"')
+ keyboard_h_lines.append(f'#define PRODUCT "{kb_info_json["keyboard_name"]} Bootloader"')
# Optional
if 'qmk_lufa_bootloader.esc_output' in kb_info_json:
diff --git a/lib/python/qmk/cli/generate/info_json.py b/lib/python/qmk/cli/generate/info_json.py
index 284d1a8510..0dc80f10cc 100755
--- a/lib/python/qmk/cli/generate/info_json.py
+++ b/lib/python/qmk/cli/generate/info_json.py
@@ -5,7 +5,7 @@ Compile an info.json for a particular keyboard and pretty-print it.
import json
from argcomplete.completers import FilesCompleter
-from jsonschema import Draft7Validator, RefResolver, validators
+from jsonschema import Draft202012Validator, RefResolver, validators
from milc import cli
from pathlib import Path
@@ -18,7 +18,7 @@ from qmk.path import is_keyboard, normpath
def pruning_validator(validator_class):
- """Extends Draft7Validator to remove properties that aren't specified in the schema.
+ """Extends Draft202012Validator to remove properties that aren't specified in the schema.
"""
validate_properties = validator_class.VALIDATORS["properties"]
@@ -37,10 +37,10 @@ def strip_info_json(kb_info_json):
"""Remove the API-only properties from the info.json.
"""
schema_store = compile_schema_store()
- pruning_draft_7_validator = pruning_validator(Draft7Validator)
+ pruning_draft_validator = pruning_validator(Draft202012Validator)
schema = schema_store['qmk.keyboard.v1']
resolver = RefResolver.from_schema(schema_store['qmk.keyboard.v1'], store=schema_store)
- validator = pruning_draft_7_validator(schema, resolver=resolver).validate
+ validator = pruning_draft_validator(schema, resolver=resolver).validate
return validator(kb_info_json)
diff --git a/lib/python/qmk/cli/import/__init__.py b/lib/python/qmk/cli/import/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/python/qmk/cli/import/__init__.py
diff --git a/lib/python/qmk/cli/import/kbfirmware.py b/lib/python/qmk/cli/import/kbfirmware.py
new file mode 100644
index 0000000000..9c03737378
--- /dev/null
+++ b/lib/python/qmk/cli/import/kbfirmware.py
@@ -0,0 +1,25 @@
+from milc import cli
+
+from qmk.importers import import_kbfirmware as _import_kbfirmware
+from qmk.path import FileType
+from qmk.json_schema import json_load
+
+
+@cli.argument('filename', type=FileType('r'), nargs='+', arg_only=True, help='file')
+@cli.subcommand('Import kbfirmware json export')
+def import_kbfirmware(cli):
+ filename = cli.args.filename[0]
+
+ data = json_load(filename)
+
+ cli.log.info(f'{{style_bright}}Importing {filename.name}.{{style_normal}}')
+ cli.echo('')
+
+ cli.log.warn("Support here is basic - Consider using 'qmk new-keyboard' instead")
+
+ kb_name = _import_kbfirmware(data)
+
+ cli.log.info(f'{{fg_green}}Imported a new keyboard named {{fg_cyan}}{kb_name}{{fg_green}}.{{fg_reset}}')
+ cli.log.info(f'To start working on things, `cd` into {{fg_cyan}}keyboards/{kb_name}{{fg_reset}},')
+ cli.log.info('or open the directory in your preferred text editor.')
+ cli.log.info(f"And build with {{fg_yellow}}qmk compile -kb {kb_name} -km default{{fg_reset}}.")
diff --git a/lib/python/qmk/cli/import/keyboard.py b/lib/python/qmk/cli/import/keyboard.py
new file mode 100644
index 0000000000..3a5ed37dee
--- /dev/null
+++ b/lib/python/qmk/cli/import/keyboard.py
@@ -0,0 +1,23 @@
+from milc import cli
+
+from qmk.importers import import_keyboard as _import_keyboard
+from qmk.path import FileType
+from qmk.json_schema import json_load
+
+
+@cli.argument('filename', type=FileType('r'), nargs='+', arg_only=True, help='file')
+@cli.subcommand('Import data-driven keyboard')
+def import_keyboard(cli):
+ filename = cli.args.filename[0]
+
+ data = json_load(filename)
+
+ cli.log.info(f'{{style_bright}}Importing {filename.name}.{{style_normal}}')
+ cli.echo('')
+
+ kb_name = _import_keyboard(data)
+
+ cli.log.info(f'{{fg_green}}Imported a new keyboard named {{fg_cyan}}{kb_name}{{fg_green}}.{{fg_reset}}')
+ cli.log.info(f'To start working on things, `cd` into {{fg_cyan}}keyboards/{kb_name}{{fg_reset}},')
+ cli.log.info('or open the directory in your preferred text editor.')
+ cli.log.info(f"And build with {{fg_yellow}}qmk compile -kb {kb_name} -km default{{fg_reset}}.")
diff --git a/lib/python/qmk/cli/import/keymap.py b/lib/python/qmk/cli/import/keymap.py
new file mode 100644
index 0000000000..a499c93480
--- /dev/null
+++ b/lib/python/qmk/cli/import/keymap.py
@@ -0,0 +1,23 @@
+from milc import cli
+
+from qmk.importers import import_keymap as _import_keymap
+from qmk.path import FileType
+from qmk.json_schema import json_load
+
+
+@cli.argument('filename', type=FileType('r'), nargs='+', arg_only=True, help='file')
+@cli.subcommand('Import data-driven keymap')
+def import_keymap(cli):
+ filename = cli.args.filename[0]
+
+ data = json_load(filename)
+
+ cli.log.info(f'{{style_bright}}Importing {filename.name}.{{style_normal}}')
+ cli.echo('')
+
+ kb_name, km_name = _import_keymap(data)
+
+ cli.log.info(f'{{fg_green}}Imported a new keymap named {{fg_cyan}}{km_name}{{fg_green}}.{{fg_reset}}')
+ cli.log.info(f'To start working on things, `cd` into {{fg_cyan}}keyboards/{kb_name}/keymaps/{km_name}{{fg_reset}},')
+ cli.log.info('or open the directory in your preferred text editor.')
+ cli.log.info(f"And build with {{fg_yellow}}qmk compile -kb {kb_name} -km {km_name}{{fg_reset}}.")
diff --git a/lib/python/qmk/constants.py b/lib/python/qmk/constants.py
index a54d9058bc..622199e46e 100644
--- a/lib/python/qmk/constants.py
+++ b/lib/python/qmk/constants.py
@@ -14,12 +14,13 @@ QMK_FIRMWARE_UPSTREAM = 'qmk/qmk_firmware'
MAX_KEYBOARD_SUBFOLDERS = 5
# Supported processor types
-CHIBIOS_PROCESSORS = 'cortex-m0', 'cortex-m0plus', 'cortex-m3', 'cortex-m4', 'MKL26Z64', 'MK20DX128', 'MK20DX256', 'MK66FX1M0', 'STM32F042', 'STM32F072', 'STM32F103', 'STM32F303', 'STM32F401', 'STM32F405', 'STM32F407', 'STM32F411', 'STM32F446', 'STM32G431', 'STM32G474', 'STM32L412', 'STM32L422', 'STM32L432', 'STM32L433', 'STM32L442', 'STM32L443', 'GD32VF103', 'WB32F3G71', 'WB32FQ95'
+CHIBIOS_PROCESSORS = 'cortex-m0', 'cortex-m0plus', 'cortex-m3', 'cortex-m4', 'MKL26Z64', 'MK20DX128', 'MK20DX256', 'MK64FX512', 'MK66FX1M0', 'RP2040', 'STM32F042', 'STM32F072', 'STM32F103', 'STM32F303', 'STM32F401', 'STM32F405', 'STM32F407', 'STM32F411', 'STM32F446', 'STM32G431', 'STM32G474', 'STM32L412', 'STM32L422', 'STM32L432', 'STM32L433', 'STM32L442', 'STM32L443', 'GD32VF103', 'WB32F3G71', 'WB32FQ95'
LUFA_PROCESSORS = 'at90usb162', 'atmega16u2', 'atmega32u2', 'atmega16u4', 'atmega32u4', 'at90usb646', 'at90usb647', 'at90usb1286', 'at90usb1287', None
VUSB_PROCESSORS = 'atmega32a', 'atmega328p', 'atmega328', 'attiny85'
# Bootloaders of the supported processors
MCU2BOOTLOADER = {
+ "RP2040": "rp2040",
"MKL26Z64": "halfkay",
"MK20DX128": "halfkay",
"MK20DX256": "halfkay",
@@ -58,6 +59,59 @@ MCU2BOOTLOADER = {
"atmega328": "usbasploader",
}
+# Map of legacy keycodes that can be automatically updated
+LEGACY_KEYCODES = { # Comment here is to force multiline formatting
+ 'RESET': 'QK_BOOT'
+}
+
+# Map VID:PID values to bootloaders
+BOOTLOADER_VIDS_PIDS = {
+ 'atmel-dfu': {
+ ("03eb", "2fef"), # ATmega16U2
+ ("03eb", "2ff0"), # ATmega32U2
+ ("03eb", "2ff3"), # ATmega16U4
+ ("03eb", "2ff4"), # ATmega32U4
+ ("03eb", "2ff9"), # AT90USB64
+ ("03eb", "2ffa"), # AT90USB162
+ ("03eb", "2ffb") # AT90USB128
+ },
+ 'kiibohd': {("1c11", "b007")},
+ 'stm32-dfu': {
+ ("1eaf", "0003"), # STM32duino
+ ("0483", "df11") # STM32 DFU
+ },
+ 'apm32-dfu': {("314b", "0106")},
+ 'gd32v-dfu': {("28e9", "0189")},
+ 'bootloadhid': {("16c0", "05df")},
+ 'usbasploader': {("16c0", "05dc")},
+ 'usbtinyisp': {("1782", "0c9f")},
+ 'md-boot': {("03eb", "6124")},
+ 'caterina': {
+ # pid.codes shared PID
+ ("1209", "2302"), # Keyboardio Atreus 2 Bootloader
+ # Spark Fun Electronics
+ ("1b4f", "9203"), # Pro Micro 3V3/8MHz
+ ("1b4f", "9205"), # Pro Micro 5V/16MHz
+ ("1b4f", "9207"), # LilyPad 3V3/8MHz (and some Pro Micro clones)
+ # Pololu Electronics
+ ("1ffb", "0101"), # A-Star 32U4
+ # Arduino SA
+ ("2341", "0036"), # Leonardo
+ ("2341", "0037"), # Micro
+ # Adafruit Industries LLC
+ ("239a", "000c"), # Feather 32U4
+ ("239a", "000d"), # ItsyBitsy 32U4 3V3/8MHz
+ ("239a", "000e"), # ItsyBitsy 32U4 5V/16MHz
+ # dog hunter AG
+ ("2a03", "0036"), # Leonardo
+ ("2a03", "0037") # Micro
+ },
+ 'hid-bootloader': {
+ ("03eb", "2067"), # QMK HID
+ ("16c0", "0478") # PJRC halfkay
+ }
+}
+
# Common format strings
DATE_FORMAT = '%Y-%m-%d'
DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S %Z'
diff --git a/lib/python/qmk/flashers.py b/lib/python/qmk/flashers.py
new file mode 100644
index 0000000000..a9cf726b44
--- /dev/null
+++ b/lib/python/qmk/flashers.py
@@ -0,0 +1,203 @@
+import shutil
+import time
+import os
+import signal
+
+import usb.core
+
+from qmk.constants import BOOTLOADER_VIDS_PIDS
+from milc import cli
+
+# yapf: disable
+_PID_TO_MCU = {
+ '2fef': 'atmega16u2',
+ '2ff0': 'atmega32u2',
+ '2ff3': 'atmega16u4',
+ '2ff4': 'atmega32u4',
+ '2ff9': 'at90usb64',
+ '2ffa': 'at90usb162',
+ '2ffb': 'at90usb128'
+}
+
+AVRDUDE_MCU = {
+ 'atmega32a': 'm32',
+ 'atmega328p': 'm328p',
+ 'atmega328': 'm328',
+}
+# yapf: enable
+
+
+class DelayedKeyboardInterrupt:
+ # Custom interrupt handler to delay the processing of Ctrl-C
+ # https://stackoverflow.com/a/21919644
+ def __enter__(self):
+ self.signal_received = False
+ self.old_handler = signal.signal(signal.SIGINT, self.handler)
+
+ def handler(self, sig, frame):
+ self.signal_received = (sig, frame)
+
+ def __exit__(self, type, value, traceback):
+ signal.signal(signal.SIGINT, self.old_handler)
+ if self.signal_received:
+ self.old_handler(*self.signal_received)
+
+
+# TODO: Make this more generic, so cli/doctor/check.py and flashers.py can share the code
+def _check_dfu_programmer_version():
+ # Return True if version is higher than 0.7.0: supports '--force'
+ check = cli.run(['dfu-programmer', '--version'], combined_output=True, timeout=5)
+ first_line = check.stdout.split('\n')[0]
+ version_number = first_line.split()[1]
+ maj, min_, bug = version_number.split('.')
+ if int(maj) >= 0 and int(min_) >= 7:
+ return True
+ else:
+ return False
+
+
+def _find_bootloader():
+ # To avoid running forever in the background, only look for bootloaders for 10min
+ start_time = time.time()
+ while time.time() - start_time < 600:
+ for bl in BOOTLOADER_VIDS_PIDS:
+ for vid, pid in BOOTLOADER_VIDS_PIDS[bl]:
+ vid_hex = int(f'0x{vid}', 0)
+ pid_hex = int(f'0x{pid}', 0)
+ with DelayedKeyboardInterrupt():
+ # PyUSB does not like to be interrupted by Ctrl-C
+ # therefore we catch the interrupt with a custom handler
+ # and only process it once pyusb finished
+ dev = usb.core.find(idVendor=vid_hex, idProduct=pid_hex)
+ if dev:
+ if bl == 'atmel-dfu':
+ details = _PID_TO_MCU[pid]
+ elif bl == 'caterina':
+ details = (vid_hex, pid_hex)
+ elif bl == 'hid-bootloader':
+ if vid == '16c0' and pid == '0478':
+ details = 'halfkay'
+ else:
+ details = 'qmk-hid'
+ elif bl == 'stm32-dfu' or bl == 'apm32-dfu' or bl == 'gd32v-dfu' or bl == 'kiibohd':
+ details = (vid, pid)
+ els