QMK FirmwareをDockerでビルドする

## 🐶 はじめに

初めての自作キーボードとして [Helix](https://yushakobo.jp/shop/helix-keyboard-kit/) を買った。
で、こういう自作キーボードは `QMK Firmware` というファームウェアによってキーマップを自由にカスタマイズできるらしいのでやってみた。

`QMK Firmware` は自分でビルドする必要があり、ビルド用環境も構築する必要があるため、記事に残しておく。

| 環境               |                  |
| ------------------ | ---------------- |
| Windows10 Pro      |                  |
| Docker for Windows | 2.3.0.2          |
| PowerShell         | 7.0.1            |
| git                | 2.27.0.windows.1 |
| QMK Firmware       | 0.9.44           |
| QMK Toolbox        | 0.0.18           |
| キーボード         | Helix            |

## 🏗 QMK 環境の構築

Windows でローカル開発環境を構築する苦行をしたくないので Docker を使う。
Docker イメージとして、必要なライブラリやソースがインストールされている [qmkfm/qmk_firmware](https://hub.docker.com/r/qmkfm/qmk_firmware) イメージを使う[^1]。

[^1]: なお、クローンした Git リポジトリから Docker イメージをビルドする方法もある（というかドキュメントではこちらの方法が記述されている）。

### `qmkfm/qmk_firmware` リポジトリから _Partial Clone_

Git リポジトリが巨大なせいでそのままクローンすると動作が鈍くなる。
そのため、カスタマイズするキーボードのディレクトリに限定してクローンする。

1. git ローカルリポジトリを作成・初期化
1. _Sparse checkout_ 機能を有効化
1. カスタマイズするキーボードのディレクトリパスのみを指定
1. リモートリポジトリ（ _fork_ 推奨）を設定してソース取得
1. 新しくブランチを作成して切り替え
1. 既存のキーマップをコピー

```shell:部分的なクローン
mkdir qmk_firmware
cd qmk_firmware
git init

git sparse-checkout init --cone
git sparse-checkout add keyboards/helix/

git remote add origin https://github.com/qmk/qmk_firmware.git
git pull --no-rebase --depth=1 origin master

git switch -c custom-helix
cp -r keyboards/helix/rev2/keymaps/default keyboards/helix/rev2/keymaps/custom
```

あとはコピーしたキーマップディレクトリ内で [QMK Configurator][qmk-conf] から出力した JSON ファイルを追加したり `keymap.c` ファイルを編集したりする。

### Docker for Windows ファイル共有設定

Docker コンテナにマウントするディレクトリを共有できるように設定する。

`Settings` -> `Resources` -> `File sharing`
を開いて共有したいディレクトリルートを指定する。

以前はボリュームディスク単位でしか設定できなかったけど、今回調べたらディレクトリ単位で指定できるようになってた。
使い勝手が良くなって嬉しい。

![docker_file_sharing](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/238841/e73bc67c-d090-d973-49a5-c94cc544518e.png "Docker File sharing")

## ⌨️ キーマップの作成

`keymap.c` を直接編集する方法だと分かりにくいので、Web ツール [QMK Configurator][qmk-conf] を使って作成した。

![qmkconfigurator.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/238841/6d4ca639-2f51-e49d-b29e-9eb62d761408.png "QMK Configurator")

本来はそのままコンパイルもできるらしいが、Helix の場合は 7/23 現在問題があってエラーとなる。
ので自分でコンパイルして `hex` ファイルを生成する必要がある。

## 🚀 コンパイル

以下のようなコンテナ設定やコマンドを記述した `docker-compose.yaml` を作成して使った。

`environment` 設定の `EXPORTED`, `KEYBOARD`, `KEYMAP` は各環境によって修正する。
もしくは `docker-compose run` 実行時に `-e KEY=VALUE` オプションで上書きする。

```yaml:/docker-compose.yaml
version: "3.8"
services:
  # JSON to C
  json2c:
    image: qmkfm/qmk_firmware
    working_dir: /qmk_firmware
    volumes:
      - ./keyboards:/qmk_firmware/keyboards
    environment:
      # Absolute path of the keymap json from QMK Configurator
      EXPORTED: /qmk_firmware/keyboards/helix/rev2/keymaps/custom/export.json
      # QMK Command: qmk json2c [-o OUTPUT] JSON_FILE
      command: /bin/bash -c 'qmk json2c $$EXPORTED > $$(dirname $$EXPORTED)/keymaps_var.c'

  # Compile keymap.c to hex file
  compile:
    image: qmkfm/qmk_firmware
    working_dir: /qmk_firmware
    volumes:
      - ./keyboards:/qmk_firmware/keyboards
      - ./.build:/qmk_firmware/.build
    environment:
      KEYBOARD: helix
      KEYMAP: custom
    # QMK Command: qmk compile -kb KEYBOARD -km KEYMAP
    command: /bin/bash -c 'qmk compile -kb $$KEYBOARD -km $$KEYMAP'
```

### JSON to C

[QMK Configurator][qmk-conf] からエクスポートした JSON ファイルを、 `.c` ファイル（今回は `keymaps_var.c`）へと変換する。

```shell:JSONキーマップファイルを.cファイルへ変換
docker-compose run --rm json2c
```

変換した `.c` ファイルではキーマップ配列 `keymaps` が定義されている。

### keymap.c のカスタマイズ

[QMK Configurator][qmk-conf] で作成したキーマップレイアウトを使うように修正する。

上記で作成された `.c` ファイル内に記述されている配列 `keymaps` を、`keymap.c` の配列 `keymaps` にコピペで上書きすればいい。

```c:keyboards/helix/rev2/keymaps/keymap.c
...

#if MATRIX_ROWS == 10 // HELIX_ROWS == 5
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
    // ここにコピペで上書き
};

...
```

Helix が 4 行版の場合は `#elif MATRIX_ROWS == 8 // HELIX_ROWS == 4` 行の下を上書き。

### 他設定

`rules.mk` ファイルの設定を編集することで LED や OLED ディスプレイを有効化/無効化できる。

### コンパイル実行

`keymap.c` ファイルの修正後、コンパイルする。

```shell:コンパイル実行
docker-compose run --rm compile
```

下記のように `.hex` ファイルが既定サイズ内で生成できれば成功。
`.hex` ファイルは `.build/` に出力される。

```shell:コンパイル成功例
Linking: .build/KEYBOARD_KEYMAP.elf                            [OK]
Creating load file for flashing: .build/KEYBOARD_KEYMAP.hex    [OK]
Copying KEYBOARD_KEYMAP.hex to qmk_firmware folder             [OK]
Checking file size of KEYBOARD_KEYMAP.hex                      [OK]
 * The firmware size is fine - 18036/28672 (62%, 10636 bytes free)
```

## 📝 ファームウェア書き込み

コンパイルされた `.hex` ファイルを _Helix_ に書き込む。

### [QMK Toolbox](https://github.com/qmk/qmk_toolbox)

ファームウェア書き込みツール [QMK Toolbox](https://github.com/qmk/qmk_toolbox/releases/latest) をインストールする。
Windows なら `qmk_toolbox.exe` をダウンロードすればいい。

```shell:qmk_toolbox.exeダウンロード
curl -LO https://github.com/qmk/qmk_toolbox/releases/download/0.0.18/qmk_toolbox.exe
```

### 書き込み作業

1. _Helix_ キーボードを接続

1. `qmk_toolbox.exe` を管理者権限で起動

1. コンパイルした `.hex` ファイルを指定

1. _MCU_ が **atmega32u4** になっていることを確認

1. _Auto-Flash_ にチェック

     リセットボタン押下後のタイミングが割とシビアなので、ここにチェックしておくと楽。

1. Helix キーボードのリセットボタンを押下

     _Auto-Flash_ にチェックしていれば自動的に書き込みが開始。
     エラーが起きたり起きなかったりするので、何回か試してください。

1. 下画像のように `flash verified` メッセージが出れば OK

     ![qmktoolbox_success_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/238841/dc23cd4e-ef8b-ae5a-dcec-1a47cf0b7412.png "Success")

---

以上でカスタムキーマップをキーボードに適用できた。

## 🐾 おわりに

QMK Configurator や Docker を有効活用して楽しようとしたら、逆に余計な時間がかかってぴえん 🥺。
まあ、キーマップをあれこれ考えてる時間に比べたら誤差みたいなものだったけど。

Docker を使えば環境によるコンパイル失敗とかがなくなるのでいいですよね！
（今回のDockerイメージは2GB近いサイズでビビりますが）
（リポジトリクローン＆サブモジュール取得すると1GB強になるので仕方ないかなって）

## 🛠 トラブルシューティング

### json2c で `[Errno 18] Invalid cross-device link: ... -> '~~.c.bak'`

リポジトリのルートディレクトリに `.bak` ファイルが作れない、とかだと思う……。
これは `docker-compose` でルートディレクトリをマウントしていないと発生した。

回避策として `qmk json2c` 実行時に `-o` オプションは使わず、リダイレクトで出力するように変更した（上述の `docker-compose.yaml` は修正済み）。

### コンパイルエラーしたキャッシュ削除したい

`make clean` の実体が `rm -rf .build` なので `.build/` を削除すればいい。

```pwsh:キャッシュ削除
Remove-Item -Recurse .build
```

### error: macro "LAYOUT" requires 64 arguments

[QMK Configurator][qmk-conf] でレイヤー番号を飛ばして定義しているとなる。
キーマップが `N/A` (KC_NO) のみだと空配列が出力されるため、必要とする配列要素が足りずにエラーとなっている。

適当なキーを 1 文字定義したり、レイヤーを飛ばさないようにする。

### QMK Toolbox で書き込みエラー

QMK Toolbox でファームウェア書き込み時にエラー。

```shell:qmk_toolbox.exeでエラー
Connecting to programmer: .avrdude.exe: ser_drain(): read error: スレッドの終了またはアプリケーションの要求によって、I/O 処理は中止されました。

avrdude.exe: ser_send(): write error: sorry no info avail
avrdude.exe: ser_recv(): read error: デバイスがコマンドを認識できません。
...
```

これは _QMK Toolbox_ を管理者権限で起動したところ、無事成功した。
……と思ったけど管理者権限でも再発した。

何度も再試行してたら突然成功したので、なんかタイミングがあるっぽい。
何度かやり直したり _QMK Toolbox_ を起動し直したり、ちょっと時間をおいてみたりするしかない？

### Windows で英語配列にすると半角全角/変換/無変換キーが使えない

US 配列に存在しないキーは無視される仕様 🥺。

代替として、`Ctrl + Space` で IME を ON/OFF するようにした。
`F13` 以降の未割当ファンクションキーを割り当てて使う方法もある。

## 参考

- [QMK Firmware Docs](https://docs.qmk.fm/#/getting_started_build_tools?id=docker)
- [QMK CLI コマンド - qmk/qmk_firmware](https://github.com/qmk/qmk_firmware/blob/master/docs/ja/cli_commands.md)
- [MakotoKurauchi/helix](https://github.com/MakotoKurauchi/helix/blob/master/Doc/firmware_jp.md)
- [リポジトリの一部だけ checkout する Git コマンド：sparse-checkout - はんなりと、ゆるやかに](https://iucstscui.hatenablog.com/entry/2020/05/21/090321)

[qmk-conf]: https://config.qmk.fm/