2017年まとめ

年末。病気とキーボードの一年だった。 病気の話は前回したから置いておいて、キーボードを振り返る。


1月、Ergodox EZを買った。その理由は首への負担軽減。 結局病気と関連してる話になるんだけど、病気になってPCもろくに触れないような時期が続いていた。 しかし1月ごろから症状が好転。またプログラミングに向かことができた。 それと同時に肩こり、首の疼痛、頭痛、眼精疲労などの症状がPCを触っていると出た。 それを少しでも緩和しようとPC環境の改善にラップトップスタンドとErgodoxを買ったのでした。

Ergodoxになれるまで数ヶ月を要しその間毎日のようにタイピング練習していた。 ある程度使えるようになり、Ergodoxに愛着が湧いてきた。 一方で打鍵音が気になるようになり、O-ringやZealencio Slient Clipなど買ったりした。 そんなこんな調べているうちに深遠なるキーボード沼の岸辺に立っていた。

岸辺でじゃぶじゃぶと遊んでいる程度だったが、Twitterをキッカケに沼の奥へと足を進めていった。 Twitterにはごろごろとすごい人達がいる。毎日のようにキーボードに関する情報が飛び交っていて、それを肴にワイワイしてる方々が目に入ってきた。 キーボードその物の魅力もそうだし、それを取り囲む方々の魅力も相まって、そこに近づいて行きたくなった。 そうしてるうちに自分もキーボードに関する知識を蓄え、技術を養い、キーボードを自作するようにまでなった。 さらにそれを売り出し、普通では関われないような方ともコミュニケーションをとれたり、縁が出来たりした。 それらの活動から得たなかで大きな自慢があるとすれば、完全オリジナルのキーボードを作れるようになったことと、RubyのMatzにフォローしてもらったことだろうか。 「MatzにTwitterでフォローされてます」の一言で転職できそうなトロフィーかもしれない。

f:id:kumatoki:20171229230449j:plain


そういえばQiitaにしょうもない記事書いてたっけな。ただ2つほどリアクションが貰えたのがあった。 RustでテトリスとSECDマシンを実装した記事。 テトリスは皆さんご存知テトリス。フロントエンドのエモい人がブクマしてくれたお陰で幾つかブクマが付いた。

qiita.com

SECDマシンは古い関数言語向けのVM。これをつかってLISPインタープリターを作った。 HaskellとRustで実装したのだが、とくに性能はでなくて、一度書いたっきりで終わりかとおもいきや、 Rustのマッチョな人がコメントとコードをくれたり、Rustの紳士な方がプルリクくれたりと、拙いコードに手を入れてくれて嬉し恥ずかしだった。

qiita.com


仕事のことについては語ることもない。 抽象的に言えば、身体が壊れたので量をこなすために質を落とした。 しかしそれではだめだと、病状が回復するに連れ思うようになった。 質と量を両立できれば一人前というか一流だろうと思う。 どちらかを取れば、どちらかが落ちる。自分はまだこのようなレベルを行ったり来たりしている。 今の仕事は自分の身体を考えれば続けられるかとても不安があるが、どのような仕事も病気の前ではそんなもんだろうとも思うが、 その考えこそ病気に支配されているといえるのかもしれない。


さて、来年はどうするか。 目の前の目標があるとすれば、完全無線レツプリの開発。 これに関しては12月後半からファームウェアの開発の下調べをしているが順調に難航している。 esp32を使えばBLE開発基板が700円くらいで買えるのでそれを採用できるようコードを読んでいるが、 初心者むけの高レベルな抽象化レイヤーが無いのでBluetoothの基礎のから学ばねばならないし、APIも実際のコードレベルになるとレイヤーが入り乱れ、かなり難しい。 ただ、もう一つ良いBLEモジュールがあって、nRF52という物でモジュール自体は1000円ほど。これに関してはAdafruitという会社が作っているArduino向けのAPIがあり、 それを使えば容易にBLEキーボードを実装できる。しかしモジュール自体はそう高くはないが、 これを実際に使いやすいように色々実装したAdafruitの開発基板となると5000円近くする。 レツプリだと2つ必要なのでそれだけ一万円とかなり値が張る。 金で開発難易度を下げるか、努力で原価を下げるか、現状では後者を実現するべく取り組んでいる。 ファームウェアさえできてしまえば、基板もケースも設計できるので見通しが立つ。 これが完成した暁にはGroupBayなどもやってみたいところだがクリティカルなディスパッチは苦手なのでたぶんやらない。 ハードの知識があれば自分でnRF52の開発基盤を作ることができ、開発費も下げれそうな物なので、esp32での開発が完了したらそちらの知識を勉強したりもしたい。 年末のコミケでは自作キーボードの手引となる薄くない本がたくさん売れたらしいので来年の界隈はもっともりあがるのだろうね


はたして、来年はキーボード沼から這い上がることができるのだろうか。 とは言え、沼から抜けた所で何すんのってなる。 何しようか。うーん。勉強したい。病気に子供の時から支配されていたので教養がなく、色々困ったりする。 何を勉強しようか。うーん。英語かな。英語はハードとソフトの開発に必須なのでこれができるかできないかで、やれることがまるで違ってくる。 どのように学べば良いかもよくわからんけど、少しずつやっていければ良いんじゃないかな。 あとは、そうだなー。うーん。セキュリティかな。CTFにすこし興味が出てきた。ジェイルブレイクとかっこいいじゃん。子供っぽいけど、実際はとても地味で芸術的。 そしてそして、うーん。筋トレか。体と心のメンテナンスを怠らないようにしなければ、また病状が悪化しかねない。病気の本質的にまた悪化するのは確定的なんだけど それでもできることはあるはず。


最後に今年はSpecial Favorite Musicというバンドを知った。 もうね、最高。目覚ましにもしてるし、寝る前にも聞いてるし、ほんと最高。 語彙がないので最高しか言えないが、今年はこの音楽が私の人生を大きく色づけた。 www.youtube.com

まぁ、今年も生き抜いただけで御の字でしょう。良いお年を。

双極性障害と言う病気を患っています。

いわゆる、躁うつ病です。うつ病と名が入っていますがその2つは症状こそ共通点があれど、全く違う病気です。 メカニズムが根本的に違います。うつ病は後天的な病気ですが躁うつ病は先天的な病気です。 つまり、うつ病は治ります。躁うつ病は治らないのです。

そんなわけでなかなか大変な持病を抱えて生まれてきてしまったのです。 子供の頃から病気に悩まされてきました。病気が僕の人生に対してとても支配的です。 蝕まれていたのは精神だけだったのでなんとか理性で心を守ることができたのですが、2016年頃から体が壊れはじめました。

最初は頭痛。頭痛が何日も治らず病院に通い始めました。アセトアミノフェンが処方されましたがまるで効かず。 次にロキソニン。ある程度効きましたが、頭痛はそれなりにストレスを感じるレベルに残余していました。 頭痛は続き、こんどは首の疼痛。痛いではなく重い。そんな感覚でした。 次第に眼精疲労がひどくなり、テレビを見ていることさえ辛くなり、しまいには不眠になりました。 とくに不眠の症状がひどいもので、寝付けない、眠りに落ちてもすぐ目覚める、現実と区別の付かない悪夢、金縛り、呼吸困難、などです。

症状はさらに悪化し、パニック障害と言う病気も引き起こしました。 所構わず、わずかなストレスを感じるだけで発作が起き、全力疾走した後のような動悸、目眩、過呼吸、震えなどの症状がでます。

これらの症状が二月ほど続き、地獄のような日々でした。

やがてある薬が処方されます。リフレックスと言う薬です。 リフレックスはNaSSAと呼ばれるタイプの抗うつ剤です。 この薬が私には劇的にフィットしました。不眠、頭痛、首の疼痛が緩和され、一気に地獄から地上へ這い上がることができたかのようでした。 しかし、症状はまだまだ残余していました。動悸、極度の疲労感、パニック、それらは午前中はかなりマシなのですが午後をすぎると顔を出し始め、 常に疲労困憊といった状態になります。そこで助けになったのがワイパックスという薬です。これはベンゾジアゼピン系の抗不安薬です。 このワイパックスも劇的に症状を改善させますが一時的なものでどうしても耐えられないときだけ使用していました。

このリフレックスワイパックス、この薬が効いたことでようやく自分は精神病なのではないかとおもいはじめました。 それまで精神疾患だと思っていなかったのです。だってストレスにさらされているわけではなかったのですから。

それまで通っていた内科のクリニックから精神科のクリニックへ移りました。 精神科の先生との最初の面談ではじっくり話を聞かれましたが薬の変更もなく診断名もなく、あまりパットした感じではありませんでした。 そのまま半年ほど時間が薬だと思い、症状を我慢しながら、薬を飲むだけの日々を過ごしていたのでした。

そして2016年の終わりごろ、あらたにエビリファイと言う薬が処方されました。1mgだけの処方で、これは抗うつ剤の補強と言う形だとおもいます。 これまた劇的にうつ状態を改善し、いままで趣味のプログラミングができなかったのができるようにまでになりました。 これでもう快方に向かうものかと思いきや、パニック発作にまた襲われ、動けなくなってしまう自体になりました。 これを先生に伝えるとこんどはSSRIと呼ばれる抗鬱剤を処方されました。 はじめはレクサプロ、これは副作用が強く出てひどい風邪を引いたようでした。 次にジェイゾロフト、これも同じような副作用がでましたがだいぶ軽めだったの二月ほど続けましたが、あまり効果が出ず。 次にパキシル、これは副作用がほとんどなく悪くないように思えましたがやはりパニックは改善せず。

SSRIの総当りの結果もむなしくパニックは残余したままでした。 そこで次の薬は炭酸リチウム、リーマスと呼ばれる古くからある薬です。 これがそこそこ効いたのです。そこで先生は家族を診察へ連れてくるよう私に言いました。 家族との話を終えた先生は私に双極2型という診断を下しました。 病気を発症してから一年ほどたったあとのことでした。

そして新たの薬が処方されます。ラミクタールです。これは双極性障害てんかんの治療に使われる非定型抗精神病薬です。 ラミクタールは中毒疹などの重篤な副作用のリスクがあるのでゆっくりと量を増やしていきます。 この薬によって病状はさらに好転していきました。パニックの頻度や重症度も軽くなり、理由のないうつ状態にも効いてきました。

現状の薬の処方はこうです。

ラミクタール 150mg、エビリファイ 2mg、メイラックス0.5mg、リフレックス7.5mg、です。

たぶんリフレックスエビリファイメイラックスはわりとすぐ減薬してやめれそうです。 しかし、まだ症状は残っていますのでラミクタールを増やすことになるか、新たな薬が増えることになるでしょう。 薬の処方がシンプルになることは病状をコントロールできているということになるのではないでしょうか。 いずれにせよ、発症初期とくらべると天国と地獄の差ほどに改善しています。これには薬に力に驚くばかりです。

しかし一年半たった今でもパニック障害はなおっておらず、電車やバスはおろか、ショッピングや車の運転もまともにできない状態です。

ひどいうつ状態の時もありました。死も視野に考えていました。しかし考えるほどに自分はもっと生きていたい。という心を感じるばかりでした。 私のような病気を持って生きていくことは並大抵のことではありませんし、私には他人にとっては普通のことがまるで、できはしないのです。 今後どのように病気と向き合っていくか、抱えていくか、ともに歩んでいくか。どのようなことになっても良いことだと思っています。 すくなくとも今はやりたいことが沢山あるのでしばらくは私をこの世にとどまらせてほしいかなと思いながら筆を一旦下ろすしましょうかね。

NISSE mini 作ったよ!

自作キーボード Advent Calendar 2017の二日目の記事です。

NISSEを小型化したキーボードを自作したので今日はそのお話。 おこがましいですが便宜上NISSE miniと名付けました。 NISSEについてはサイトを見てください。

NISSEを作られているshikiさんのblogにこのような記事があります。

エルゴノミック キーボードの簡単な歴史

この記事で説明されている通りNISSEは長いキーボード研究を参考に作られているのがわかります。 最近自作キーボードをチョットダケデキルようになったので作ってみようと思い立ったわけです。

コンセプト

そのまんまNISSEでは私の好みに合わないところがあります。 まずは大きさ。ファンクションキーなどはほぼ使わないので削除。 さらに数字行、これは私の手の大きさでは届かない行なのでこれも削除。 両サイドの記号列も手がとどかないので削除。

さて、減らした数字や記号、時々たまに使うかもしれないFNキーなどはどうするの? そう思われる方もいるかもしれませんが、ちゃんと入力できるので後述。

キー数も減らしたら、ボディのサイズも小さくできるので余分な所は削除。

さてさて、つまりはNISSEを小さくしたNISSE miniが私の作りたい物のようです。 コンセプトが決まった所で形をつくっていきましょう。

デザイン

私が使えるのはfusion360という3dcadソフトなのでそれでスケッチを書いていきます。

まずはキースイッチ。 スイッチ自体の大きさは14x14mmくらいです。 そこに乗るキーキャプのサイズが18x18mmです。 さらにキーキャプどうしが干渉しないようにスペースが必要ですね。それを1mm。 キースイッチの上蓋をあとから外せるようにするためのスキマもあけて。 それらをひとまとめにしたのがこちら。

f:id:kumatoki:20171027203600p:plain

これをパターンとコピペを駆使してならべていくわけです。 キー配置はNISSEをざっくりモデリングしました。 具体的にはNISSEの画像を先程のスケッチに寸法を合わせるように拡大して、手でポチポチならべます。 必要分並べたら外枠もトレース。 できあがりがこれ。

f:id:kumatoki:20171027203610p:plain

外枠が大きいままです。 NISSEでは上方向と内側方向に傾斜がついています。 この傾斜はとても重要で、エルゴノミクスの一部です。 斜め下の辺を基準に傾斜をつけているのではないかと思います。 なのでできるだけ斜め下の辺をイジらないように最小化できないかと考えましたが、今回アクリル加工をしてもらったサービスの都合上、寸法に制限があったのでなかなか都合良くはいかず。 ネジ穴など開けて、最終的なスケッチがこれ。

f:id:kumatoki:20171027203622p:plain

これの5枚制作。5枚っていうのもサービスの都合上。 できあがったアクリルプレートがこれ。

f:id:kumatoki:20171027204017j:plain

形が見えてきました。

キースイッチはんだ付け

今回はGateron BlueにJSpacersというスイッチ内部に仕込むO-Ring的な物を付けました。 これの取り付け作業がとてもたいへん。

f:id:kumatoki:20171027204315j:plain

プレートにスイッチをはめ込んでいきましょう。ポチポチと。 はめ込んだらはんだ付けです。 キーマトリックスダイオードなどについて説明しません。

横と縦と配線を張り巡らせていきます。

f:id:kumatoki:20171028172712j:plain

Pro Micro

キーボードの脳となるのがPro Microとよばれるマイコン。 ほかのマイコンを使ってもよいのでしょうが、値段的にも、後述するファームウェア的にもこれが手軽です。ピン数が少ないので対応できるキー数が少ないのが傷。IO Expanderなどでカバーしましょう。

f:id:kumatoki:20171028172729j:plain

今回はこれをプレート裏にエポキシ樹脂接着剤で固定して、これに配線をしていきます。

f:id:kumatoki:20171028172753j:plain

QMK Firmware

からっぽの脳に知性を宿しましょう。QMK Firmwareです。 簡単なファームウェアなら自作もそう難しくはないのですが、すでに私はErgodoxやLet's splitなどでqmk firmwareの便利さを知っていたので、今回もこれを採用。

QMK対応のやり方は別記事

さて、上記の削除されていったキー達。ちゃんと入力する方法があるんです。 LayerとよばれるQMKの機能を多用して入力できます。 簡単に言えば修飾キーみたいなもんです。 あるキーを押しながら別のキーを押すと、入力できる文字がある。それだけのことです。 具体的にはLowerキーを押しながら「q」を押すと「!」が。Raiseキーを押しながら「q」を押すと「1」が。 こんな具合に削除されていったキーを入力することができます。 もちろんキーマップも書き換え可能なのでそれらのパターンは自分で無数に定義できるわけです。

出来上がったキーマップはこちら。 記号はplanckのdefaultの配列が秀逸だと気がついたのでそれを採用。 MISCレイヤーに矢印や音量調整、音楽再生、マウスカーソル移動などの雑多なキーを配置。 真ん中4つにはよく使うショートカット、タブ移動とアプリ移動。 あれ、ファンクションキーが結局いらない子でしたね。。。

#define XXXX KC_NO
#define ____ KC_TRNS

// for MacOS
#define NEXTTAB ACTION_MODS_KEY(MOD_LGUI, KC_RCBR)
#define PREVTAB ACTION_MODS_KEY(MOD_LGUI, KC_LCBR)
#define NEXTAPP ACTION_MODS_KEY(MOD_LCTL, KC_RGHT)
#define PREVAPP ACTION_MODS_KEY(MOD_LCTL, KC_LEFT)

enum Layer {
  TOP,
  LOWER,
  RAISE,
  MISC,
};

const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
  [TOP] = {
    {KC_TAB,        KC_Q,             KC_W,    KC_E,      KC_R,   KC_T,    PREVTAB,  NEXTTAB,  KC_Y,    KC_U,   KC_I,      KC_O,    KC_P,              KC_BSPC},
    {CTL_T(KC_ESC), LT(MISC, KC_A),   KC_S,    KC_D,      KC_F,   KC_G,    PREVAPP,  NEXTAPP,  KC_H,    KC_J,   KC_K,      KC_L,    LT(MISC, KC_SCLN), KC_QUOT},
    {KC_EQL,        KC_Z,             KC_X,    KC_C,      KC_V,   KC_B,    XXXX,     XXXX,     KC_N,    KC_M,   KC_COMM,   KC_DOT,  KC_SLSH,           KC_MINS},
    {XXXX,    XXXX,     KC_LGUI,       MO(LOWER), SFT_T(KC_SPC), KC_LANG2, KC_LALT,
     KC_RALT, KC_LANG1, SFT_T(KC_ENT), MO(RAISE), KC_RGUI,       XXXX,     XXXX}
  },

  [LOWER] = {
    {____, KC_EXLM, KC_AT, KC_HASH, KC_DLR, KC_PERC, ____, ____, KC_CIRC, KC_AMPR, KC_ASTR, KC_LPRN, KC_RPRN, ____},
    {____, ____,    ____,  ____,    ____,   ____,    ____, ____, ____,    KC_UNDS, KC_PLUS, KC_LCBR, KC_RCBR, ____},
    {____, ____,    ____,  ____,    ____,   ____,    XXXX, XXXX, ____,    KC_TILD, KC_PIPE, KC_QUOT, ____,    ____},
    {XXXX, XXXX,    ____,  ____,    ____,   ____,    ____, ____, ____,    ____,    ____,    ____,    XXXX,    XXXX},
 
  },

  [RAISE] = {
    {____, KC_1, KC_2, KC_3, KC_4, KC_5, ____, ____, KC_6, KC_7,    KC_8,    KC_9,    KC_0,    ____},
    {____, ____, ____, ____, ____, ____, ____, ____, ____, KC_MINS, KC_EQL,  KC_LBRC, KC_RBRC, ____},
    {____, ____, ____, ____, ____, ____, XXXX, XXXX, ____, KC_GRV,  KC_BSLS, KC_DQT,  ____,    ____},
    {XXXX, XXXX, ____, ____, ____, ____, ____, ____, ____, ____,    ____,    ____,    XXXX,    XXXX},
  },

  [MISC] = {
    {RESET, ____, KC_WH_U, KC_MS_U, KC_WH_D, KC_PAUS, ____, ____, KC_HOME, KC_PGDN, KC_UP,   KC_PGUP,  ____,    ____},
    {____,  ____, KC_MS_L, KC_MS_D, KC_MS_R, KC_INS,  ____, ____, KC_END,  KC_LEFT, KC_DOWN, KC_RIGHT, ____,    ____},
    {____,  ____, ____,    ____,    ____,    ____,    XXXX, XXXX, KC_MPRV, KC_MPLY, KC_MNXT, KC_MUTE,  KC_VOLD, KC_VOLU},
    {XXXX,  XXXX, ____,    ____,    KC_BTN1, KC_BTN2, ____, ____, PREVTAB, NEXTTAB, ____,    ____,     XXXX,    XXXX}
  }
};

仕上げ

Pro MicroにQMKを書き込み一通りテストを終えたら、ボディを組み立て。 ボトムプレートはあまったトッププレートを流用するというだいぶかっこ悪い感じですが、妥協です。 二枚を接着。接着面がわずかなので強度ゼロですが、これも妥協。 持ち運ばないし、まぁいいよね。必要になったら補強します。

f:id:kumatoki:20171028173316j:plain

fusion360上でスペーサーの寸法出しておいたので、スペーサーを噛ませて傾斜をつけてネジを締めれば完成。できあがりです。やったね!

f:id:kumatoki:20171027204409j:plain

感想

楽しく作れました。とくにCAD上での設計は楽しい。 砂場で遊ぶ子供のように無邪気に作っては壊しを繰り返して少しずつ要領を掴んでいきました。 そのまま3Dプリントでボディを作ろうかと思いましたが値段がかなり掛かるので今回はアクリルとスペーサーを使いましたが複雑な造形を作ろうとすれば3Dプリントが必須になるでしょう。

傾斜のつき方が上方向には十分取れたのですが、内側方向には不十分となってしまいました。 プレートの設計を妥協したのでしょうがないね。

まぁ、そんな具合に仕上がりました。次はPCBの設計にでもチャレンジしてみようかな。

(この記事はMacbookのキーボードで書かれました)

自作キーボードをQMK Firmwareに対応させる

pro microとUnix系OSの使用を前提として話をすすめる。

まずはcloneしてnew_project.shでテンプレートを作る。

❯ git clone https://github.com/qmk/qmk_firmware
❯ cd qmk_firmware
❯ util/new_project.sh your_keyboard_name
❯ cd keyboard/your_keyboard_name

次にconfig.hを編集。 MATRIX_ROWSMATRIX_COLSをそれぞれ数合わせ。

#define CATERINA_BOOTLOADERを追加しておくとRESETキーが使えるようになる。 BOOTLOADER = caterinaもrule.mk ファイルに追加しましょう。

MATRIX_ROW_PINSMATRIX_COL_PINSは以下の画像の紫色の背景のP〇〇がポート番号なのでそれらを列挙。

f:id:kumatoki:20171030180002p:plain

次はお好きにkeymaps/default/keymap.cを定義。

たぶんpro microではピンの数が足りないのでIO Expanderを使ったり、pro microを2つ使ったりして、その2つを通信する必要があるわけです。それらがめんどくさいという方はteensyを使うといいかもしれない。ピン数が多くて事足りるかもしれない。

IO Expanderを使うのであればまずはi2cかSPIの実装が必要です。 さらに使用するIO Expanderの使い方を覚えなくてはいけません。 MCP23017と言うIO Expanderを使うのであれば、こちらの実装を借りると良いかもです。

alter

pro microを2つ使うのであればlet's splitの実装が参考になるかもしれません。

lets_split

❯ cd ../..
❯ make your_keyboard_name:default:avrdude

qmk_firmwareディレクトリトップに戻ってmakeします。

書き込みが成功したらとりあえずQMKに対応です

自作キーボードができるまで

f:id:kumatoki:20170925120214j:plain 他の写真

ババーン

これ、私が作りました。すごいでしょ。 batmanっぽいのでbatkeysと名付けました。

デザイン的にはkeyboardioとnisseを模範している。 それらをさらにコンパクトかつ高さを低く抑えた形となった。

使い勝手としては、タイピング速度測定サイトで記録を更新することができたのでいい方と言ってもいいだろう。気になると思うのは親指キー周り。率直なところ親指に役割を持たせすぎると辛い。自分では3キーくらいがベストだと思う。がキーを少なくしすぎたのでどうしても親指をフル活用せざる追えない。まぁそのうち慣れてくるでしょう。

回顧録

さて自作キーボードという世界がある。世の中に普及しているキーボードは人間工学的に好ましくないものが多い。そこで流行ったのがErgodox、最近ではlet's split。しかしこれでも満足しない人たちがいる。そういう人たちが足を踏み入れるのが自作キーボードという世界。

私もその一人だ。と言いたいがそうでもなく、ただ作って見たかったというだけでそもそもプログラマですらなければデスクワーカーでもない。本当に趣味で、好奇心で、作りたかっただけなのでした。

始まりは、twitterでキーボードを作り始めている方を見かけたから。その方はないんさんと言う方でキーボードのモックアップを最初に作られていた。そのキーボードは当時自分が使っていたergodoxと比べてとても小さく、さらに左右分離式で、親指をうまく活用できるようキーを配置してあった。それはとてもかっこよくてSNS引きこもりな自分がついコメントしてしまうほど魅力的だった。しかしその方は、さらに基盤やケース、ファームウェアも自分で作られていて、とても自分が真似できるようなことでは無いとも思った。

とは思いつつも電子工作に子供の頃から漠然とした興味があったので、少しずつ初めてみることにした。 まずはArduino unoを買って簡単なプログラムを動かした。highとlow、vccやgnd、抵抗やLEDやタクトスイッチ。とても簡単なところから一つずつ試していってゆっくり体で覚えて言った。正直なところ、それがなぜそうなるのか、もっと根本的な原理はなんなのか、までは知らない。ただそうすればそう動くと覚えていった。

少しずつ慣れていき、次第にキーボードに焦点を当てていった。キーボードの根本はキーマトリックスというものとそれをPC側へキー入力として伝えるマイコン。この二つを覚えればキーボードの仕組みはほぼ覚えたようなものだった。

あとは具体的にどのように実用的な形にしていくか。ここが自分にとっては難しいところだった。 デザインはどうすればいいのか。デザインをどう形にすればいいのか。イマイチ踏み出せないでいた。

そんなこんなしてる間に、他の方のキーボードが出てき始めていた。 ゆかりさんhrhgさん言った方々のとてもかっこ良いものがさらに自分を刺激した。 このお二人に共通しているのは3DPrinterを使って媒体を作られていること。 これで簡単にケースが作れると知って、fusion360という3DCadソフトを覚えることにした。 このソフトとても高機能ではあるがそのぶん難しい。しかし自分のデザインが形となっている様はとても楽しく、それに身を任せてすぐに使い方を覚えることができた。

デザインをする上で参考としていたのがesrilleのnisse。それを作られている方のブログ。 Shiki's Weblog この方のブログを読み、左右分離型を前提としていた考えを変え、一体型にすることにした。 まあ、技術的にも一体型の方が楽だというのもある。

dmm.makeで3DPrintしてもらうことができたのでそこに注文した。値段は4k~5k。まあ、40キーしかないこのサイズだからこの値段で済んでるとも言える。あとはスイッチやキーキャプ、マイコンなどを用意しケースの出来上がりを待つだけ。届いたらあとは根気よく半田付け。これで出来上がり。 今回配線に使ったポリウレタン銅線は少し半田付けにコツがいるようで、最初は苦労したがtwitterでゆかりさんにコツを教えていただいたら、すぐにできるようになったのでありがたい。

ハードが出来上がればあとはソフト。キーマトリックスを読み、それを元にキーコードを発行していく。 コンパクトなキーボードに欠かせないのがレイヤー。これもすんなり実装できた。もう一つ必要な機能があってそれはqmk firmwareではmod tapと呼ばれる機能で一つのキーに修飾キーと普通の文字キーの二つを役割を与える機能。これの実装に手間取った。というかすぐに諦めた。そこで大人しくqmk firmwareに対応させることにした。これも前述のないんさんが記事にされているのでそれにしたがって進めて言った。途中わからないところがあったがすぐにないんさんにフォローしていただいた。おかげでqmk対応もすんなりできたのでキーボードとして満足に機能させることができた。

@matsPodさんにRedditにポストしていただいので海外の方からの反応もいただくことができた。英語わかんないので雰囲気だけだけどね。 reddit

先人たちのアドバイスのおかげで自分もキーボードというものを作り上げることができた。 感謝したい。

elmでライフゲーム

elm

リアクティブプログラミングを軸にした関数型言語であり、Javascriptへ変換されるいわゆるAltJS。 破壊的な操作も関数的に扱える仕組みはモナドではない新しいタイプの操作体系で、Elm Architectureと呼ばれる。

そんなelmに入門。個人的に新しい言語を覚えるときはHello worldの代わりにLifegameを書いているので、例によってelmでもライフゲームから始める。

ライフゲーム

お堅い呼び名はセルオートマトン。実態は単細胞シミュレーション。 あるセルの状態とその周囲の状態からルールを元に次の状態を決定していく。 そのルールは、

  • あるセルの状態1であり、周囲に2つの状態1があれば、次のセルの状態は1。
  • あるセルの状態1であり、周囲に3つの状態1があれば、次のセルの状態は1。
  • あるセルの状態0であり、周囲に3つの状態1があれば、次のセルの状態は1。
  • それ以外は、セルの状態は0となる。

コード

https://github.com/kmtoki/lifegame-elm

レコードと呼ばれる、JSのオブジェクトのようなものでまとめて状態管理。 セルの実態はArray (Array Bool)と2次元配列。

module Lifegame exposing (..)

import Array exposing (Array)


type alias Lifegame =
    { size : { y : Int, x : Int }
    , count : Int
    , isContiune : Bool
    , cells : Array (Array Bool)
    }

init : Int -> Int -> Lifegame
init y x =
    { size = { y = y, x = x }
    , count = 0
    , isContiune = False
    , cells = Array.repeat y <| Array.repeat x False
    }

上記のルールを関数化したものがこれ。

rule : Bool -> Int -> Bool
rule b n =
    case ( b, n ) of
        ( True, 2 ) ->
            True

        ( True, 3 ) ->
            True

        ( False, 3 ) ->
            True

        _ ->
            False

周囲の状態を調べるのがenv。 ルールと周囲の状態を元にセルを更新するのがnext。

env : Int -> Int -> Lifegame -> Int
env y x lg =
    let
        arounds =
            [ ( y - 1, x - 1 )
            , ( y - 1, x )
            , ( y - 1, x + 1 )
            , ( y, x - 1 )
            , ( y, x + 1 )
            , ( y + 1, x - 1 )
            , ( y + 1, x )
            , ( y + 1, x + 1 )
            ]

        f ( y, x ) =
            case Array.get y lg.cells of
                Nothing ->
                    0

                Just xs ->
                    case Array.get x xs of
                        Nothing ->
                            0

                        Just b ->
                            if b then
                                1
                            else
                                0
    in
        List.sum <| List.map f arounds


next : Lifegame -> Lifegame
next lg =
    let
        cells =
            Array.indexedMap
                (\y xs ->
                    Array.indexedMap
                        (\x b -> rule b <| env y x lg)
                        xs
                )
                lg.cells
    in
        { lg
            | count = lg.count + 1
            , cells = cells
        }

ライフゲームの実態は以上のコードで済む。 そのライフゲームをブラウザ上でアニメーションすることがelmでは簡単にできる。 Model Update Viewを基本とし、Subscriptionsでタイマーなどの副作用のある操作などをすることができる。

Modelを各関数で引き回し、それを元にViewがHtmlを生成し、ブラウザ上のイベントをMsgという形でupdateへ送信し、各操作へ振り分けていく。

面白いのが副作用の扱い方。副作用をSub/Cmdで包み関数の返り値として返すとElm Architectureが裏側でよしなにしてくれる。OCamlHaskellとも違うながらも純粋関数的な程を保っている。

今回のコードではランダムとタイマーしか使っていないが、この仕組みでWebsocketなども扱える豊かな表現力ある。

module Main exposing (..)

import Html as H
import Html.Attributes as HA
import Html.Events as HE
import Svg as S
import Svg.Attributes as SA
import Array exposing (Array)
import Time
import List
import String
import Random
import Lifegame


main =
    H.program
        { init = init
        , update = update
        , view = view
        , subscriptions = subscriptions
        }


type alias Model =
    Lifegame.Lifegame

type Msg
    = New
    | SetY String
    | SetX String
    | Start
    | Next
    | Stop
    | Random
    | RandomSet (Array (Array Bool))


init : ( Model, Cmd Msg )
init =
    ( Lifegame.init 0 0, Cmd.none )


subscriptions : Model -> Sub Msg
subscriptions model =
    if model.isContiune then
        Time.every (Time.millisecond * 100) <| always Next
    else
        Sub.map (always Stop) Sub.none


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        New ->
            ( Lifegame.init model.size.y model.size.x, Cmd.none )

        SetY s ->
            case String.toInt s of
                Ok n ->
                    ( { model | size = { x = model.size.x, y = n } }, Cmd.none )

                Err _ ->
                    ( model, Cmd.none )

        SetX s ->
            case String.toInt s of
                Ok n ->
                    ( { model | size = { y = model.size.y, x = n } }, Cmd.none )

                Err _ ->
                    ( model, Cmd.none )

        Start ->
            ( { model | isContiune = True }, Cmd.none )

        Next ->
            ( Lifegame.next model, Cmd.none )

        Stop ->
            ( { model | isContiune = False }, Cmd.none )

        RandomSet cells ->
            ( { model | cells = cells }, Cmd.none )

        Random ->
            let
                gen =
                    Random.map Array.fromList <|
                        Random.list model.size.y <|
                            Random.map Array.fromList <|
                                Random.list model.size.x Random.bool
            in
                ( model, Random.generate RandomSet gen )


view : Model -> H.Html Msg
view model =
    H.div []
        [ H.div []
            [ H.input [ HA.type_ "number", HA.placeholder "y", HE.onInput SetY ] []
            , H.input [ HA.type_ "number", HA.placeholder "x", HE.onInput SetX ] []
            , H.button [ HE.onClick New ] [ H.text "New" ]
            , H.button [ HE.onClick Start ] [ H.text "Start" ]
            , H.button [ HE.onClick Stop ] [ H.text "Stop" ]
            , H.button [ HE.onClick Random ] [ H.text "Random" ]
            ]
        , H.div [] [ H.text (toString model.count) ]
        , H.div [] [ viewLifegame model ]
        ]


viewLifegame : Model -> H.Html Msg
viewLifegame model =
    S.svg
        [ SA.width (toString <| model.size.x * 50)
        , SA.height (toString <| model.size.y * 50)
        ]
        (List.concat <|
            Array.toList <|
                Array.indexedMap
                    (\y xs ->
                        Array.toList <|
                            Array.indexedMap
                                (\x b ->
                                    S.rect
                                        [ SA.x (toString <| x * 5)
                                        , SA.y (toString <| y * 5)
                                        , SA.width "5"
                                        , SA.height "5"
                                        , SA.fill
                                            (if b then
                                                "green"
                                             else
                                                "black"
                                            )
                                        , SA.stroke "black"
                                        ]
                                        []
                                )
                                xs
                    )
                    model.cells
        )

出来上がり

f:id:kumatoki:20170731172704g:plain

所感

元々はFRPベースだったらしく、小難しい概念で副作用を取り扱っていようだがElm Architectureとやらのおかげでずいぶん簡単に入門できた。パターンマッチばかりで関数合成が少なくなってしまったが語彙の問題なので覚えればすぐ簡潔にかけるようになるだろう。elm-formatにはちょっと趣味と合わない部分があるが、まあ人様に見やすく編集しやすいコード書式という意味でelm-formatで統一的に合わせていくのは良いと思う。
HTMLイベントの発火の分、Msgを追加していかないといけないのは面倒なのでどうにかまとめたいが方法がまだよくわからない。あとTaskという概念もあるらしく、非同期通信などを使う場合は必要になってくるらしい。Haskellで関数型の思考には慣れているので書き味は程よく気持ちのいいものであったが、Haskellと比べ、型安全なライブラリ設計なので冗長的な部分も多々ある。型クラスの導入も議論されているらしいがHaskellの標準ライブラリの型クラス化などを見ている限り、なかなか型クラスも面倒なところがありそうなので、無いなら無いで良いのかもしれない。データ操作が明示的なのも、書くときは面倒だが、読むときに楽できるので良い部分もある。
フロントエンドの知識はまるでないので、これを機会に少し覚えていけたらと思うだけで特に勉強しない結末。

let's split キーマップ

  /* Qwerty
   * ,-------------------------------------------, ,------------------------------------------,
   * | Tab   |   Q  |   W  |   E  |   R   |   T  | |   Y   |   U  |   I  |   O  |   P  |  BS  |
   * |-------+------+------+------+-------+------| |-------+------+------+------+------+------|
   * |Esc/Ctl| A/Md |   S  |   D  |   F   |   G  | |   H   |   J  |   K  |   L  |;:/Md |'"/Ctl|
   * |-------+------+------+------+-------+------| |-------+------+------+------+------+------|
   * |  =+   |   Z  |   X  |   C  |   V   |   B  | |   N   |   M  |  ,<  |  .>  |  /?  |  -_  |
   * |-------+------+------+------+-------+------| |-------+------+------+------+------+------|
   * |   `~  | Raise| Alt  | GUI  | Lower |Sp/Sft| |Ent/Sft| Lower| GUI  | ALT  | Raise|  \|  |
   * `-------------------------------------------' `------------------------------------------'
   */

  /* Lower 
   * ,-----------------------------------------, ,-----------------------------------------,
   * |      |   1  |   2  |   3  |   4  |   5  | |   6  |   7  |   8  |   9  |   0  |      |
   * |------+------+------+------+------+------| |------+------+------+------+------+------|
   * | PREV |   !  |   @  |   #  |   $  |   %  | |   ^  |   &  |   *  |   (  |   )  | NEXT |
   * |------+------+------+------+------+------| |------+------+------+------+------+------|
   * |      |   ~  |   `  |   |  |   \  |      | |      |   [  |   ]  |   {  |   }  |      |
   * |------+------+------+------+------+------| |------+------+------+------+------+------|
   * |      |      |      |      | Eisu |      | |      | Kana |      |      |      |      |
   * `-----------------------------------------' `-----------------------------------------'
   */

  /* Raise 
   * ,-----------------------------------------, ,-----------------------------------------.
   * |  F1  |  F2  |  F3  |  F4  |  F5  |  F6  | |  F7  |  F8  |  F9  |  F10 |  F11 |  F12 |
   * |------+------+------+------+------+------| |------+------+------+------+------+------|
   * |      |      |      |      |      |      | |      |      |      |      |      |      |
   * |------+------+------+------+------+------| |------+------+------+------+------+------|
   * |      |      |      |      |      |      | |      |      |      |      |      |      |
   * |------+------+------+------+------+------| |------+------+------+------+------+------|
   * |      |      |      |      |      |      | |      |      |      |      |      |      |
   * `-----------------------------------------' `-----------------------------------------'
   */

  /* Media 
   * ,-----------------------------------------, ,-----------------------------------------.
   * |      |      | WhUp | MsUp | WhDn |      | |  End | PgDn |  Up  | PgUp | Home |      |
   * |------+------+------+------+------+------| |------+------+------+------+------+------|
   * |Reset |      | MsLf | MsDn | MsRg |      | |      | Left | Down | Right|      |Reset |
   * |------+------+------+------+------+------| |------+------+------+------+------+------|
   * |      |      |      |      |      |      | | Prev |StpPly| Next |Mute  |VolDn |VolUp |
   * |------+------+------+------+------+------| |------+------+------+------+------+------|
   * |      |      |      |      |Click2|Click1| |      |      |      |      |      |      |
   * `-----------------------------------------' `-----------------------------------------'
   */

keymap.c

最初に

qmk_firmwareとは多様なキーボードに対応するための汎用ファームウェアであり、自分でキーマップを変えることができる。さらに普通のキーボードにはない機能があり、レイヤー、タップ/ホールド、タップダンス、マクロなどがある。

私のレツプリキーマップ

レツプリことlet’s split、48のキーをどう効率よく使えるか、は悩ましく楽しい問題。 48キーで十分と言えるのは一重に「レイヤー」と呼ばれる機能のおかげ。このレイヤーをうまく使い各々のキーマップを作り上げていける。 私の場合、4つのレイヤーを作っている。アルファベットやスペース、エンターなどの基本キーを配置したトップレイヤー、記号を散りばめたLowerレイヤー、ほぼ使われないFnキーのRaiseレイヤー、マウスカーソルやクリック、アローキーや音楽再生と音量調整ができるMediaレイヤー。

Qwerty

トップレイヤーはコード上ではQwertyレイヤーと読んでいる。今時わざわざQwerty配列なんぞ使わんでもよかろうと言う気もしなくはないが、新しい配列を覚えるコストは非常に高く、さらに既存のQwerty配列を前提に設計されたソフトウェアを多用する身としてはなかなか抜け出せない。

Qwertyレイヤーの肝は親指にある。親指スペースは当たり前だが、さらにエンターを持ってきた。さらにこの2キーはスペースとエンターとして機能すると同時にシフトとしても機能する。 どう言うことかと言う これはキーをタップした場合とホールドした場合で挙動を分けられる機能。 これによって一つのキーに二つの役割を与えることができる。よって親指シフトを実現することができている。

英数とかなの位置はなかなか難しく、当初はQwertyレイヤーに置いていたが誤入力した場合に非常に煩わしいのでLowerレイヤーに置くことにした。しかし多用するキーを入力するのに2つのキーを押さなければならないと言うのは少し納得が行かない面もある。

WindowsでのIME切り替えはAlt + `で全角/半角ができる

Lower

次にLowerレイヤーについて。プログラミングするので記号は全て多用する。その中でもさらによく使う記号を良い位置に置いておきたいがある程度既存の配列と互換性を持たせて置くと、配列を覚えやすいのでそれを優先した結果が上記の配列になっている。defaultの配列ではLowerとRaiseに記号を分けているので少々覚えるのにコストがかかるのではないかと思い、Lowerのみに記号を詰め込んだ。

NEXTTABとPREVTABとは、文字どうりタブ移動を一つのキーにまとめたもの。 MacOSでしか使えないショートカットだと思うのでWindowsではCtrl+Tab / Ctrl+Tab+Shiftで同様のことができる(多分。

// MacOS only
#define NEXTTAB ACTION_MODS_KEY(MOD_LGUI, KC_RCBR)
#define PREVTAB ACTION_MODS_KEY(MOD_LGUI, KC_LCBR)

Raise

Raiseレイヤーはファンクションキーを割り当てている。 そもそもレイヤー切り替えは脳に負担がかかるものだと思うので、少ないほど良いと思う。

Media

Aか;をホールドすると使える、 Mediaレイヤーはなかなか便利なもので、ホームポジションから手を動かさずマウス操作ができるのでかなり便利。キーボードからマウス操作もできるのもqmk_firmwareの機能。ただ矢印上と右か左を同時押しででカーソルがロックと言うか動かなくなるのでどうにかならないかな。