From fc7e9efd215fee7ec8acd3e2740a6ab9d4633818 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Sat, 13 Aug 2022 14:39:56 +0100 Subject: [PATCH] Improve importer workflow (#17707) --- docs/hand_wire.md | 29 ++++--- lib/python/qmk/constants.py | 5 ++ lib/python/qmk/importers.py | 147 +++++++++++++++++++++++------------- 3 files changed, 118 insertions(+), 63 deletions(-) diff --git a/docs/hand_wire.md b/docs/hand_wire.md index e79a80375ac..06809254df2 100644 --- a/docs/hand_wire.md +++ b/docs/hand_wire.md @@ -177,20 +177,25 @@ From here, you should have a working keyboard once you program a firmware. Simple firmware can be created easily using the [Keyboard Firmware Builder](https://kbfirmware.com/) website. Recreate your layout using [Keyboard Layout Editor](https://www.keyboard-layout-editor.com), import it and recreate the matrix (if not already done as part of [planning the matrix](#planning-the-matrix). -Go through the rest of the tabs, assigning keys until you get to the last one where you can compile and download your firmware. The .hex file can be flashed straight onto your keyboard, and the .zip of source files can be modified for advanced functionality and compiled locally using the method described in [Building Your First Firmware](newbs_building_firmware?id=build-your-firmware). +Go through the rest of the tabs, assigning keys until you get to the last one where you can compile and download your firmware. The .hex file can be flashed straight onto your keyboard, or for advanced functionality, compiled locally after [Setting up Your Environment](newbs_getting_started.md). -The source given by Keyboard Firmware Builder is QMK, but is based on a version of QMK from early 2017. To compile the code from your .zip file in a modern version of QMK Firmware, you'll need to open the .zip and follow these instructions: +The source given by Keyboard Firmware Builder is QMK, but is based on a version of QMK from early 2017. To compile the firmware in a modern version of QMK Firmware, you'll need to export via the `Save Configuration` button, then run: + + qmk import-kbfirmware /path/to/export.json + +For example: + +``` +$ qmk import-kbfirmware ~/Downloads/gh62.json +Ψ Importing gh62.json. + +⚠ Support here is basic - Consider using 'qmk new-keyboard' instead +Ψ Imported a new keyboard named gh62. +Ψ To start working on things, `cd` into keyboards/gh62, +Ψ or open the directory in your preferred text editor. +Ψ And build with qmk compile -kb gh62 -km default. +``` -1. Extract the `kb` folder to `qmk_firmware/keyboards/handwired/`. -2. Open the extracted `kb` folder, then proceed to the `keymaps/default/` folder, and open `keymap.c`. -3. Locate and delete the `action_get_macro` code block: - ``` - const macro_t *action_get_macro(keyrecord_t *record, uint8_t id, uint8_t opt) { - ... - return MACRO_NONE; - } - ``` -4. Save and close `keymap.c`. ## Flashing the Firmware diff --git a/lib/python/qmk/constants.py b/lib/python/qmk/constants.py index 95fe9a61d08..5fe8326daf7 100644 --- a/lib/python/qmk/constants.py +++ b/lib/python/qmk/constants.py @@ -58,6 +58,11 @@ MCU2BOOTLOADER = { "atmega328": "usbasploader", } +# Map of legacy keycodes that can be automatically updated +LEGACY_KEYCODES = { # Comment here is to force multiline formatting + 'RESET': 'QK_BOOT' +} + # Common format strings DATE_FORMAT = '%Y-%m-%d' DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S %Z' diff --git a/lib/python/qmk/importers.py b/lib/python/qmk/importers.py index f9ecac02aeb..307c66ee3cc 100644 --- a/lib/python/qmk/importers.py +++ b/lib/python/qmk/importers.py @@ -1,10 +1,26 @@ from dotty_dict import dotty +from datetime import date +from pathlib import Path import json +from qmk.git import git_get_username from qmk.json_schema import validate from qmk.path import keyboard, keymap -from qmk.constants import MCU2BOOTLOADER +from qmk.constants import MCU2BOOTLOADER, LEGACY_KEYCODES from qmk.json_encoders import InfoJSONEncoder, KeymapJSONEncoder +from qmk.json_schema import deep_update, json_load + +TEMPLATE = Path('data/templates/keyboard/') + + +def replace_placeholders(src, dest, tokens): + """Replaces the given placeholders in each template file. + """ + content = src.read_text() + for key, value in tokens.items(): + content = content.replace(f'%{key}%', value) + + dest.write_text(content) def _gen_dummy_keymap(name, info_data): @@ -18,7 +34,47 @@ def _gen_dummy_keymap(name, info_data): "layers": [["KC_NO" for _ in range(0, layout_length)]], } - return json.dumps(keymap_data, cls=KeymapJSONEncoder) + return keymap_data + + +def _extract_kbfirmware_layout(kbf_data): + layout = [] + for key in kbf_data['keyboard.keys']: + item = { + 'matrix': [key['row'], key['col']], + 'x': key['state']['x'], + 'y': key['state']['y'], + } + if key['state']['w'] != 1: + item['w'] = key['state']['w'] + if key['state']['h'] != 1: + item['h'] = key['state']['h'] + layout.append(item) + + return layout + + +def _extract_kbfirmware_keymap(kbf_data): + keymap_data = { + 'keyboard': kbf_data['keyboard.settings.name'].lower(), + 'layout': 'LAYOUT', + 'layers': [], + } + + for i in range(15): + layer = [] + for key in kbf_data['keyboard.keys']: + keycode = key['keycodes'][i]['id'] + keycode = LEGACY_KEYCODES.get(keycode, keycode) + if '()' in keycode: + fields = key['keycodes'][i]['fields'] + keycode = f'{keycode.split(")")[0]}{",".join(map(str, fields))})' + layer.append(keycode) + if set(layer) == {'KC_TRNS'}: + break + keymap_data['layers'].append(layer) + + return keymap_data def import_keymap(keymap_data): @@ -40,7 +96,7 @@ def import_keymap(keymap_data): return (kb_name, km_name) -def import_keyboard(info_data): +def import_keyboard(info_data, keymap_data=None): # Validate to ensure we don't have to deal with bad data - handles stdin/file validate(info_data, 'qmk.api.keyboard.v1') @@ -55,17 +111,36 @@ def import_keyboard(info_data): if kb_folder.exists(): raise ValueError(f'Keyboard {{fg_cyan}}{kb_name}{{fg_reset}} already exists! Please choose a different name.') + if not keymap_data: + # TODO: if supports community then grab that instead + keymap_data = _gen_dummy_keymap(kb_name, info_data) + keyboard_info = kb_folder / 'info.json' - keyboard_rules = kb_folder / 'rules.mk' keyboard_keymap = kb_folder / 'keymaps' / 'default' / 'keymap.json' - # This is the deepest folder in the expected tree + # begin with making the deepest folder in the tree keyboard_keymap.parent.mkdir(parents=True, exist_ok=True) + user_name = git_get_username() + if not user_name: + user_name = 'TODO' + + tokens = { # Comment here is to force multiline formatting + 'YEAR': str(date.today().year), + 'KEYBOARD': kb_name, + 'USER_NAME': user_name, + 'REAL_NAME': user_name, + } + # Dump out all those lovely files - keyboard_info.write_text(json.dumps(info_data, cls=InfoJSONEncoder)) - keyboard_rules.write_text("# This file intentionally left blank") - keyboard_keymap.write_text(_gen_dummy_keymap(kb_name, info_data)) + for file in list(TEMPLATE.iterdir()): + replace_placeholders(file, kb_folder / file.name, tokens) + + temp = json_load(keyboard_info) + deep_update(temp, info_data) + + keyboard_info.write_text(json.dumps(temp, cls=InfoJSONEncoder)) + keyboard_keymap.write_text(json.dumps(keymap_data, cls=KeymapJSONEncoder)) return kb_name @@ -77,21 +152,12 @@ def import_kbfirmware(kbfirmware_data): mcu = ["atmega32u2", "atmega32u4", "at90usb1286"][kbf_data['keyboard.controller']] bootloader = MCU2BOOTLOADER.get(mcu, "custom") - layout = [] - for key in kbf_data['keyboard.keys']: - layout.append({ - "matrix": [key["row"], key["col"]], - "x": key["state"]["x"], - "y": key["state"]["y"], - "w": key["state"]["w"], - "h": key["state"]["h"], - }) + layout = _extract_kbfirmware_layout(kbf_data) + keymap_data = _extract_kbfirmware_keymap(kbf_data) # convert to d/d info.json - info_data = { + info_data = dotty({ "keyboard_name": kbf_data['keyboard.settings.name'].lower(), - "manufacturer": "TODO", - "maintainer": "TODO", "processor": mcu, "bootloader": bootloader, "diode_direction": diode_direction, @@ -99,50 +165,29 @@ def import_kbfirmware(kbfirmware_data): "cols": kbf_data['keyboard.pins.col'], "rows": kbf_data['keyboard.pins.row'], }, - "usb": { - "vid": "0xFEED", - "pid": "0x0000", - "device_version": "0.0.1", - }, - "features": { - "bootmagic": True, - "command": False, - "console": False, - "extrakey": True, - "mousekey": True, - "nkro": True, - }, "layouts": { "LAYOUT": { "layout": layout, } } - } + }) if kbf_data['keyboard.pins.num'] or kbf_data['keyboard.pins.caps'] or kbf_data['keyboard.pins.scroll']: - indicators = {} if kbf_data['keyboard.pins.num']: - indicators['num_lock'] = kbf_data['keyboard.pins.num'] + info_data['indicators.num_lock'] = kbf_data['keyboard.pins.num'] if kbf_data['keyboard.pins.caps']: - indicators['caps_lock'] = kbf_data['keyboard.pins.caps'] + info_data['indicators.caps_lock'] = kbf_data['keyboard.pins.caps'] if kbf_data['keyboard.pins.scroll']: - indicators['scroll_lock'] = kbf_data['keyboard.pins.scroll'] - info_data['indicators'] = indicators + info_data['indicators.scroll_lock'] = kbf_data['keyboard.pins.scroll'] if kbf_data['keyboard.pins.rgb']: - info_data['rgblight'] = { - 'animations': { - 'all': True - }, - 'led_count': kbf_data['keyboard.settings.rgbNum'], - 'pin': kbf_data['keyboard.pins.rgb'], - } + info_data['rgblight.animations.all'] = True + info_data['rgblight.led_count'] = kbf_data['keyboard.settings.rgbNum'] + info_data['rgblight.pin'] = kbf_data['keyboard.pins.rgb'] if kbf_data['keyboard.pins.led']: - info_data['backlight'] = { - 'levels': kbf_data['keyboard.settings.backlightLevels'], - 'pin': kbf_data['keyboard.pins.led'], - } + info_data['backlight.levels'] = kbf_data['keyboard.settings.backlightLevels'] + info_data['backlight.pin'] = kbf_data['keyboard.pins.led'] # delegate as if it were a regular keyboard import - return import_keyboard(info_data) + return import_keyboard(info_data.to_dict(), keymap_data)