Skip to content

Commit fc7e9ef

Browse files
authored
Improve importer workflow (#17707)
1 parent a02aff9 commit fc7e9ef

File tree

3 files changed

+120
-65
lines changed

3 files changed

+120
-65
lines changed

docs/hand_wire.md

+19-14
Original file line numberDiff line numberDiff line change
@@ -177,20 +177,25 @@ From here, you should have a working keyboard once you program a firmware.
177177

178178
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).
179179

180-
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).
181-
182-
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:
183-
184-
1. Extract the `kb` folder to `qmk_firmware/keyboards/handwired/`.
185-
2. Open the extracted `kb` folder, then proceed to the `keymaps/default/` folder, and open `keymap.c`.
186-
3. Locate and delete the `action_get_macro` code block:
187-
```
188-
const macro_t *action_get_macro(keyrecord_t *record, uint8_t id, uint8_t opt) {
189-
...
190-
return MACRO_NONE;
191-
}
192-
```
193-
4. Save and close `keymap.c`.
180+
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).
181+
182+
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:
183+
184+
qmk import-kbfirmware /path/to/export.json
185+
186+
For example:
187+
188+
```
189+
$ qmk import-kbfirmware ~/Downloads/gh62.json
190+
Ψ Importing gh62.json.
191+
192+
⚠ Support here is basic - Consider using 'qmk new-keyboard' instead
193+
Ψ Imported a new keyboard named gh62.
194+
Ψ To start working on things, `cd` into keyboards/gh62,
195+
Ψ or open the directory in your preferred text editor.
196+
Ψ And build with qmk compile -kb gh62 -km default.
197+
```
198+
194199

195200
## Flashing the Firmware
196201

lib/python/qmk/constants.py

+5
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@
5858
"atmega328": "usbasploader",
5959
}
6060

61+
# Map of legacy keycodes that can be automatically updated
62+
LEGACY_KEYCODES = { # Comment here is to force multiline formatting
63+
'RESET': 'QK_BOOT'
64+
}
65+
6166
# Common format strings
6267
DATE_FORMAT = '%Y-%m-%d'
6368
DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S %Z'

lib/python/qmk/importers.py

+96-51
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,26 @@
11
from dotty_dict import dotty
2+
from datetime import date
3+
from pathlib import Path
24
import json
35

6+
from qmk.git import git_get_username
47
from qmk.json_schema import validate
58
from qmk.path import keyboard, keymap
6-
from qmk.constants import MCU2BOOTLOADER
9+
from qmk.constants import MCU2BOOTLOADER, LEGACY_KEYCODES
710
from qmk.json_encoders import InfoJSONEncoder, KeymapJSONEncoder
11+
from qmk.json_schema import deep_update, json_load
12+
13+
TEMPLATE = Path('data/templates/keyboard/')
14+
15+
16+
def replace_placeholders(src, dest, tokens):
17+
"""Replaces the given placeholders in each template file.
18+
"""
19+
content = src.read_text()
20+
for key, value in tokens.items():
21+
content = content.replace(f'%{key}%', value)
22+
23+
dest.write_text(content)
824

925

1026
def _gen_dummy_keymap(name, info_data):
@@ -18,7 +34,47 @@ def _gen_dummy_keymap(name, info_data):
1834
"layers": [["KC_NO" for _ in range(0, layout_length)]],
1935
}
2036

21-
return json.dumps(keymap_data, cls=KeymapJSONEncoder)
37+
return keymap_data
38+
39+
40+
def _extract_kbfirmware_layout(kbf_data):
41+
layout = []
42+
for key in kbf_data['keyboard.keys']:
43+
item = {
44+
'matrix': [key['row'], key['col']],
45+
'x': key['state']['x'],
46+
'y': key['state']['y'],
47+
}
48+
if key['state']['w'] != 1:
49+
item['w'] = key['state']['w']
50+
if key['state']['h'] != 1:
51+
item['h'] = key['state']['h']
52+
layout.append(item)
53+
54+
return layout
55+
56+
57+
def _extract_kbfirmware_keymap(kbf_data):
58+
keymap_data = {
59+
'keyboard': kbf_data['keyboard.settings.name'].lower(),
60+
'layout': 'LAYOUT',
61+
'layers': [],
62+
}
63+
64+
for i in range(15):
65+
layer = []
66+
for key in kbf_data['keyboard.keys']:
67+
keycode = key['keycodes'][i]['id']
68+
keycode = LEGACY_KEYCODES.get(keycode, keycode)
69+
if '()' in keycode:
70+
fields = key['keycodes'][i]['fields']
71+
keycode = f'{keycode.split(")")[0]}{",".join(map(str, fields))})'
72+
layer.append(keycode)
73+
if set(layer) == {'KC_TRNS'}:
74+
break
75+
keymap_data['layers'].append(layer)
76+
77+
return keymap_data
2278

2379

2480
def import_keymap(keymap_data):
@@ -40,7 +96,7 @@ def import_keymap(keymap_data):
4096
return (kb_name, km_name)
4197

4298

43-
def import_keyboard(info_data):
99+
def import_keyboard(info_data, keymap_data=None):
44100
# Validate to ensure we don't have to deal with bad data - handles stdin/file
45101
validate(info_data, 'qmk.api.keyboard.v1')
46102

@@ -55,17 +111,36 @@ def import_keyboard(info_data):
55111
if kb_folder.exists():
56112
raise ValueError(f'Keyboard {{fg_cyan}}{kb_name}{{fg_reset}} already exists! Please choose a different name.')
57113

114+
if not keymap_data:
115+
# TODO: if supports community then grab that instead
116+
keymap_data = _gen_dummy_keymap(kb_name, info_data)
117+
58118
keyboard_info = kb_folder / 'info.json'
59-
keyboard_rules = kb_folder / 'rules.mk'
60119
keyboard_keymap = kb_folder / 'keymaps' / 'default' / 'keymap.json'
61120

62-
# This is the deepest folder in the expected tree
121+
# begin with making the deepest folder in the tree
63122
keyboard_keymap.parent.mkdir(parents=True, exist_ok=True)
64123

124+
user_name = git_get_username()
125+
if not user_name:
126+
user_name = 'TODO'
127+
128+
tokens = { # Comment here is to force multiline formatting
129+
'YEAR': str(date.today().year),
130+
'KEYBOARD': kb_name,
131+
'USER_NAME': user_name,
132+
'REAL_NAME': user_name,
133+
}
134+
65135
# Dump out all those lovely files
66-
keyboard_info.write_text(json.dumps(info_data, cls=InfoJSONEncoder))
67-
keyboard_rules.write_text("# This file intentionally left blank")
68-
keyboard_keymap.write_text(_gen_dummy_keymap(kb_name, info_data))
136+
for file in list(TEMPLATE.iterdir()):
137+
replace_placeholders(file, kb_folder / file.name, tokens)
138+
139+
temp = json_load(keyboard_info)
140+
deep_update(temp, info_data)
141+
142+
keyboard_info.write_text(json.dumps(temp, cls=InfoJSONEncoder))
143+
keyboard_keymap.write_text(json.dumps(keymap_data, cls=KeymapJSONEncoder))
69144

70145
return kb_name
71146

@@ -77,72 +152,42 @@ def import_kbfirmware(kbfirmware_data):
77152
mcu = ["atmega32u2", "atmega32u4", "at90usb1286"][kbf_data['keyboard.controller']]
78153
bootloader = MCU2BOOTLOADER.get(mcu, "custom")
79154

80-
layout = []
81-
for key in kbf_data['keyboard.keys']:
82-
layout.append({
83-
"matrix": [key["row"], key["col"]],
84-
"x": key["state"]["x"],
85-
"y": key["state"]["y"],
86-
"w": key["state"]["w"],
87-
"h": key["state"]["h"],
88-
})
155+
layout = _extract_kbfirmware_layout(kbf_data)
156+
keymap_data = _extract_kbfirmware_keymap(kbf_data)
89157

90158
# convert to d/d info.json
91-
info_data = {
159+
info_data = dotty({
92160
"keyboard_name": kbf_data['keyboard.settings.name'].lower(),
93-
"manufacturer": "TODO",
94-
"maintainer": "TODO",
95161
"processor": mcu,
96162
"bootloader": bootloader,
97163
"diode_direction": diode_direction,
98164
"matrix_pins": {
99165
"cols": kbf_data['keyboard.pins.col'],
100166
"rows": kbf_data['keyboard.pins.row'],
101167
},
102-
"usb": {
103-
"vid": "0xFEED",
104-
"pid": "0x0000",
105-
"device_version": "0.0.1",
106-
},
107-
"features": {
108-
"bootmagic": True,
109-
"command": False,
110-
"console": False,
111-
"extrakey": True,
112-
"mousekey": True,
113-
"nkro": True,
114-
},
115168
"layouts": {
116169
"LAYOUT": {
117170
"layout": layout,
118171
}
119172
}
120-
}
173+
})
121174

122175
if kbf_data['keyboard.pins.num'] or kbf_data['keyboard.pins.caps'] or kbf_data['keyboard.pins.scroll']:
123-
indicators = {}
124176
if kbf_data['keyboard.pins.num']:
125-
indicators['num_lock'] = kbf_data['keyboard.pins.num']
177+
info_data['indicators.num_lock'] = kbf_data['keyboard.pins.num']
126178
if kbf_data['keyboard.pins.caps']:
127-
indicators['caps_lock'] = kbf_data['keyboard.pins.caps']
179+
info_data['indicators.caps_lock'] = kbf_data['keyboard.pins.caps']
128180
if kbf_data['keyboard.pins.scroll']:
129-
indicators['scroll_lock'] = kbf_data['keyboard.pins.scroll']
130-
info_data['indicators'] = indicators
181+
info_data['indicators.scroll_lock'] = kbf_data['keyboard.pins.scroll']
131182

132183
if kbf_data['keyboard.pins.rgb']:
133-
info_data['rgblight'] = {
134-
'animations': {
135-
'all': True
136-
},
137-
'led_count': kbf_data['keyboard.settings.rgbNum'],
138-
'pin': kbf_data['keyboard.pins.rgb'],
139-
}
184+
info_data['rgblight.animations.all'] = True
185+
info_data['rgblight.led_count'] = kbf_data['keyboard.settings.rgbNum']
186+
info_data['rgblight.pin'] = kbf_data['keyboard.pins.rgb']
140187

141188
if kbf_data['keyboard.pins.led']:
142-
info_data['backlight'] = {
143-
'levels': kbf_data['keyboard.settings.backlightLevels'],
144-
'pin': kbf_data['keyboard.pins.led'],
145-
}
189+
info_data['backlight.levels'] = kbf_data['keyboard.settings.backlightLevels']
190+
info_data['backlight.pin'] = kbf_data['keyboard.pins.led']
146191

147192
# delegate as if it were a regular keyboard import
148-
return import_keyboard(info_data)
193+
return import_keyboard(info_data.to_dict(), keymap_data)

0 commit comments

Comments
 (0)