🧊

prk_firmware で自分のためのキーボードを書く

2021/12/19に公開

これは2021年PRK Firmwareアドベントカレンダーの 19 日目の記事です。前後の記事は、カレンダーから辿ってください。

今ちまたの Rubyist の中では、スピードキューブが静かなブームです。
キューブにはまりすぎてキーボードができなくなった人、キーボードがいそがしてくてキューブを回せない人というのが
でてきている頃ではないでしょうか。

しかし、我々はキューブとキーブを両方やらないといけないわけです。
ということで、キューブを回すときに必要なものをキーボードで作ることによって、
キューブを回しながらキーボードもできないかと考えました。

スピードキューブは、解くスピードを計ることで自分の成長を感じることができます。
そこで、キーボードでスピードキューブ用のタイマーを作れば、両方できてお得ということです。
そんなとき、prk_firmware は Ruby でプログラミングできるので、普段 Ruby を書いている我々にとってはピッタリなファームウェアだといえるでしょう。

今回タイマー用に使うキーボードは、Cassette42を使います。Cassette42 は、4 キーと 2 ロータリーエンコーダー、OLED ということで、キューブを回すときに必要なものをキーボードで作ることによって、
いろいろなことができそうなマクロパッドです。残念ながら、今の prk_firmware は OLED はサポートされていないため使えません。

まず、時間を計る機能を考えます。prk_firmware 内の時間は、board_millis という関数で取得できます。
この関数は、たぶんボードが起動してからの msec を返してくれるので、これを利用して、スタートとストップの時間を取得し経過時間を計算できます。

時間の取得方法がわかれば、これを利用して Timer モデルを作成します。
Cassette42 は 4 キーあるので、タイマーのスタート/ストップ用のメソッド、計測した時間を表示するためのメソッド、AO5 (直近 5 回のうち最大と最小の時間を除いた 3 回の平均値)を計算し表示するメソッド、
過去の計測をリセットするメソッドを用意します。

class CubeTimer
  def start_and_stop; end
  def latest; end
  def ao5; end
  def reset_session; end
end

キーを押したときに、これらのメソッドを発火できるように、Keyboard#define_mode_key メソッドでキーの設定をしていきます。

timer = CubeTimer.new

kbd.define_mode_key :TIMER_START_STOP, [ Proc.new { timer.start_and_stop          }, :KC_NO, 300, nil ]
kbd.define_mode_key :AO5,              [ Proc.new { kbd.macro timer.ao5           }, :KC_NO, 300, nil ]
kbd.define_mode_key :LAST,             [ Proc.new { kbd.macro timer.latest        }, :KC_NO, 300, nil ]
kbd.define_mode_key :TIMER_RESET,      [ Proc.new { kbd.macro timer.reset_session }, :KC_NO, 300, nil ]

Keyboard#define_mode_key の第一引数は、キーマップを設定するときに指定する識別子になります。第二引数の配列は、先頭から、
キーを押したときに実行される処理またはキー、キーを話した時に実行される処理またはキー、長押し判定をするための時間、複数回押されたときに複数回押されたと判定するための時間という形になります。
この第二引数の配列の先頭に Prco を使って処理を記述すれば、キーが押されたときに実行される処理を登録できるわけです。
また、今回は Keyboard#macro メソッドをつかうことで、計測した時間がキー入力として入力されるようにしています。

キーの定義ができたら、次はそれらを使ってキーマップを以下のように設定します。

kbd.add_layer :default, %i[
  TIMER_RESET AO5 LAST TIMER_START_STOP KC_ENT KC_ENT
]

最後の KC_ENT 2 つは、ロータリーエンコーダーをプッシュしたときのキーになります。
これで、タイマー用のキーの設定は完了です。

今回、タイマーのスタートとストップを同じキーでできるようにしています。そのため、このままではタイマーが作動しているのか、止まっているのか判別することが困難です。
そこで、Cassette42 についているアンダーグローの LED を利用したいと思います。タイマーが作動している間は光り、タイマーが止まったら消すというようにしたいと思います。
まずは、LED 用の RGB クラスのインスタンスを作り、Keyboard インスタンスへの追加と CubeTimer のインスタンス内での保持を行います。

rgb = RGB.new(
  0,    # pin number
  5,    # size of underglow pixel
  0,   # size of backlight pixel
  false # 32bit data will be sent to a pixel if true while 24bit if false
)

kbd.append rgb
timer = CubeTimer.new(rgb)

光らせたいときは、RGB#effect メソッドで光り方を指定します。光り方については、Wikiページ 参照してください。
LED を消す場合は、RGB#turn_off メソッドで消すことができます。ただ光っていているだけで意味をなさない LED でなく、こういう意味のある LED はいいですね。

最終的な keymap.rb は、https://gist.github.com/takkanm/2fdb652dc2bb88b91d4ac2b97ef60e7b になります。

実際の動作については、以下の動画を参考にしてください。

ソルブしている最中の様子。動画時間を短くしようと、久しぶりに 2x2x2 を使ったが、久しぶりすぎてできていたころの倍ぐらい時間かかっている。

https://www.youtube.com/watch?v=Jg5V2zurF1k

結果を表示させている様子。AO5A が欠けてしまっているが、もう一度 5 回計る気力がなかったので、そのままである。

https://youtu.be/apYpd5mowvk

このように、prk_firmware を使えば、ただのキー入力のできるキーボードだけでなく、いろんなことを簡単にプログラミングしやすくなります。

と良い面だけを書きましたが、いろいろ制約もあり考えないといけないこともあります。本来のキューブようのタイマーは、両手を置いた状態から離したらスタート、揃え終わったら両手で叩いてストップとなります。
これを再現しようとしたのですが、どうやら処理が大きくなりすぎたのか Overflow MAX_SYMBOLS_COUNT for XXXX というメッセージがでてしまいました。
処理をインライン化したりいろいろしましたが、そのメッセージを消すことができなかったので、タイムアップとしワンキーだけやるようにしたのでした。
あと、やはり OLED があれば、そこに秒数とかを表示できるので便利だったといえます。

ということで、みなさんもキーボードもスピードキューブもどちらもやっていきましょう。

スピードキューブに興味を持ったみなさんは、まずは以下の商品を手に入れることをお勧めします。だいたい送料込み 2000 円ぐらいで、回しやすいキューブと解くためのわかりやすいテキストが入手できます。

Discussion