Happy Hacking KeyboardをBluetooth化する
PFUのHappy Hacking Keyboard Professional2(HHKB)を購入しました。
今までキーボードはMacbookのもので十分だと感じてましたが、一度触ってみるとキーボードにこだわる人の気持ちがわかる、とても素晴らしいキーボードです。
今回はせっかくならば無線化したいということでBluetooth化してみました。
※当方電子工作は初心者なため、以下の内容の正しさは全く保証出来ません。
また、改造に伴う機器の故障等も自己責任でお願いします。
使用するソフトウェアは全てGitHub上 https://github.com/addsict/YetAnotherHHKBController にあります。
必要なもの
- Arduino Pro mini 5V(16MHz)
- RN-42 HID(Bluetoothモジュール)*1
- AS1322A 昇圧型DC-DCコンバータモジュール
- 10kΩ, 5.1kΩの抵抗
- 3.3V出力 三端子レギュレータ
- 単四電池2本
- その他ユニバーサル基板, ケーブル, スイッチなど
ArduinoでHHKBのコントローラを作成する
HHKBのキータッチを自由にハンドリングするためにはキーボードのコントローラを自前で用意する必要があります。
マイコンの知識は余りないので、今回は手軽にプロトタイピングができるArduinoを使用することにしました。
キーボードのコントローラは、
- キーが物理的に押されているかどうかをチェックし
- 押されていればその信号をキーボードの接続先デバイス(パソコンなど)に送る
という役割を持ちます。
キーボードというと、キーが押されたら電気信号が流れ、それによってコントローラが動くイベント駆動的なものだと直感的には思いますが、実際はとても短い間隔(HHKBの場合15ms)で全てのキーを1つ1つ押されているかチェックする、ポーリング処理を行なっています。
コントローラの処理を擬似コードで書くとこのようになります。
while(1) { for (全てのキー) { check_key_pressed(); if (押されていたら) { send_key_code_to_computer(); } } }
全てのキーをチェックするにはその分だけマイコンのGPIOピンの数が必要ですが、実際は「キーの数 >> マイコンのGPIOピンの数」なので、キーはマトリックス状(行×列)に配置しマルチプレクサを介してキーを選択します。
例えば64個のキー(行8 × 列8)がある場合、行と列を2進数で表せば合計6本のGPIOがあれば十分です。
while(1) { start = timer_start(); for (行から1つ選択) { for (列から1つ選択) { check_key_pressed(); if (押されていたら) { send_key_code_to_computer(); } } } while(timer_end() - start <= 15); }
15ms毎にキーのスキャンを行うタイマーコードも含めました。
完全なコードはこちらにあります。
コントローラ周りに関しては、自作キーボードの作り方を説明しているShiggy Enterprisesというサイトが参考になりました。
BluetoothとHIDキーボード
Bluetoothは使用したいサービスによって様々なプロファイルを使い分けますが、
Bluetoothキーボードでよく使われるプロファイルはHID(Human Interface Device Profile)とSPP(Serial Port Profile)のどちらかです。
HIDはホストマシンにあらかじめドライバなどを入れておく必要がないので、改造するにあたってはHIDプロファイルが手っ取り早そうです。
HIDキーボードではキーボードのキーの状態を、HIDレポートと呼ばれる8バイトのパケットでホストマシンに伝えます。 HIDレポートの構成は以下のようになっています。
| 0 | 1 | 2 ~ 7 | |----------------|----------------|---------------| | modifier keys | reseverd(0x00) | keycode 1 ~ 6 | |----------------|----------------|---------------|
- 0バイト目: modifier keys
- 修飾キー(ShiftやControlキーなどのこと)を1ビットずつ論理和(OR)を取ったものを入れます
- 1バイト目: reserved
- 常に0x00が入ります
- 2 ~ 7バイト目: keycode1 ~ 6
- 実際に押されているキー(修飾キーを除く)を入れます
たとえば「左Shift(0xE1) + 左Command(0xE3) + a(0x04) + b(0x05)」が押されている場合、
0x0A #0xE1を0x01, 0xE3を0x03にし論理和を取った値
0x00
0x04
0x05
0x00
0x00
0x00
0x00
をホストマシンに送ります。
USBキーボードの同時押しは最大6キーまでとなっているのが多いですが、それはこのHIDレポートの構造から来ているのでした。
HIDレポートでは単純に現在押されているキーを送ればよく、「どのキーが新規に押された」「押されていたこのキーが離された」などのキーの状態を把握する必要がないので、
コントローラ側の実装はシンプルになります。これらのキーの状態の管理はキーのバウンシングも含めてホストマシン側でやってくれます。
HIDキーボードで使うキーコードの仕様は http://www.usb.org/developers/devclass_docs/Hut1_11.pdf (P.53〜)を参照してください。
今回使用したBluetoothモジュール、RN-42HIDはArduinoとUART(シリアル)で通信が出来るようになっています。
Arduino側はHIDレポートを組み立て、それをシリアル通信でRN-42HIDに送信するだけで、Bluetoothのパケットのペイロードに入れて送ってくれます。
コントローラはBluetoothのことを一切気にする必要がなくシリアルで送りたい信号を送ればいいので、実際の通信路がUSBケーブルであろうとBluetoothであろうと透過的に扱うことが出来るので便利です。
回路を製作しHHKBと接続する
電気回路・電気工作については全くの初心者なので余り深くは触れません。詳しくは写真から判断して下さい。
気を付けなければいけない点としては、
- HHKBのVccは5V、RN-42HIDのVccは3.3Vなので5V, 3.3Vの両方が必要
- 電池1本1.5Vなので2本直列に繋げても3Vにしかならない。昇圧回路AS1322Aで5Vにする
- 5Vにした入力電圧は三端子レギュレータで3.3Vに落としRN-42HIDのVccとする
- RN-42HIDのUARTは3.3Vの電圧なので、ArduinoのTXからRN-42HIDのRXに入力される信号を3.3Vに降圧させる
UARTを3.3Vに降圧するために、5.1kΩと10kΩの抵抗で分圧回路を作りRXの入力電圧にしています。
コントローラとHHKBとの接続に関してはtmkさんのInternal of HHKB proを非常に参考にさせて頂きました。
ホストマシンに接続する
回路を組み立てたら電源スイッチをONにしホストマシンとペアリングを行います。 ペアリング後以下のように接続されていればBluetooth化完了です。
HHKBは見た目もそうですが中身も非常にコンパクトになっていて、電池や回路を中に組み込むのに苦労しました。
require.jsとdata-main属性
require.jsを使っていて少しハマったのでメモ書き。
requre()関数をトップレベルのHTML(moduleを宣言していないHTML)に使用する場合、 data-main属性でconfigファイルを読み込もうとすると失敗します。
このような場合です。
<script src="require.js" data-main="require-config.js"></script> <script> require([ 'jquery', ], function ($) { console.log($); }); </script> // require-config.js requirejs.config({ paths: { jquery: 'jquery-1.8.2.min' }, });
これはrequire.jsファイルを読み込んだ後、data-main属性に指定されたファイルを読み込む前にrequire()関数が評価されてしまうためです。
configが読み込まれるまでrequire()関数は待ってくれるはずだと勝手に思い込んでいました。
回避策としては、ドキュメントにも書かれているようにrequire.jsを読み込む前にあらかじめconfigを設定しておけばokです。
事前に設定する場合は、requireという変数に設定項目をオブジェクトとして渡しておきます。
<script src="require-config.js"></script> <script src="require.js"></script> <script> require([ 'jquery', ], function ($) { console.log($); }); </script> // require-config.js var require = { paths: { jquery: 'jquery-1.8.2.min' }, };
Mac OS X wi-fi tips
Mac OSではネットワークの設定はLocationという単位で切り替えることができます。
terminalからは /usr/sbin/scselect コマンドで現在の設定を確認できます。
$ scselect xxxxxxxx-44F6-4EFE-BE5C-B5E0B91B7CDC (foo) * xxxxxxxx-1D83-4E30-A533-77C2CE1E59EA (Automatic) xxxxxxxx-FAE4-4D87-9014-D1012E0807A5 (bar)
scselectコマンドの後にLocation名を書くとその設定に切り替わります。
自分は頻繁に切り替えるのでエイリアスを設定しておきました。
$ echo 'ailas foo-net="scselect foo"' >> ~/.bashrc
またwi-fiの調子が悪い時はAirMacのON-OFFを繰り返すとたまに改善することがあるので、
これもエイリアスに登録しておきます。
AirMacの操作は /usr/sbin/networksetup コマンドを使うと可能です。
$ echo 'alias wifi="networksetup -setairportpower en0"' >> ~/.bashrc # en0はMacBookの場合です。 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ $ wifi on # AirMac起動 $ wifi off # AirMac停止
【iOS】バックグラウンドでBluetoothのコネクションを維持する
iOSデバイスでのBluetooth通信は基本アプリが画面内に表示されている'フォアグラウンド'の時のみ有効です。
今回はアプリがバックグラウンドに隠れた間でも、Bluetooth通信を可能にする裏技的な(?)tipsを紹介します。
バックグラウンドで実行可能な処理
まずiOSでバックグラウンド実行が可能なものを見てみましょう。
iOSでは、バックグラウンドで行える処理が以下のものに限られています。
Apple iOS App Programming Guide
- 音楽の再生
- 位置情報の取得
- VoIP
- Newsstandを使用するアプリ
- 外部アクセサリから定期的に情報をアップデートするアプリ
このような、バックグラウンドでの動作が重要なサービスに限り許されています。
iOS Bluetoothフレームワークの種類
iOSが提供するBluetoothのフレームワークには大きく分けて2種類あります。
名称 | 使用可能なデバイス | バックグラウンド通信 |
---|---|---|
Game Kit | Bluetoothが使用可能なiPhone全て | × |
Core Bluetooth | iPhone4S以降 | ○ |
CoreBluetoothはBluetooth LE(Low Energy)に対応した外部機器との通信のみに使われます。
今回のターゲットはGame Kitフレームワークでのバックグラウンド通信です。
Bluetoothのコネクションを維持する方法
ではバックグラウンド通信を可能にするにはどうしたらいいのでしょうか。
実は、音楽をバックグラウンドで再生している間は、Bluetoothの通信が可能となっています。
以下、無音の音楽を永遠と再生し続けるサンプルコードです。
// オーディオデータフォーマットを表す構造体を作成 AudioStreamBasicDescription dummyASBD; dummyASBD.mSampleRate = 44100.0; dummyASBD.mFormatID = kAudioFormatLinearPCM; dummyASBD.mFormatFlags = kAudioFormatFlagsCanonical; dummyASBD.mBytesPerPacket = 4; dummyASBD.mFramesPerPacket = 1; dummyASBD.mBytesPerFrame = 4; dummyASBD.mChannelsPerFrame = 2; dummyASBD.mBitsPerChannel = 16; dummyASBD.mReserved = 0; OSStatus status; // AudioQueueの作成 status = AudioQueueNewOutput(&dummyASBD, dummyAudioQueueCallBack, self, NULL, NULL, 0, &dummyAudioQueue); if (status) { NSLog("error @AudioQueueNewOutput"); } // 再生 status = AudioQueueStart(dummyAudioQueue, NULL); if (status) { NSLog(@"error @AudioQueueStart"); }
実際にバックグラウンドで試すときは、アプリのinfo.plistのバックグラウンドモードの項目にAudio Playが設定されていることを確かめてください。
<key>UIBackgroundModes</key> <array> <string>audio</string> </array>
この状態であれば、GKSessionクラスのsendDataToAllPeers:withDataMode:error:
メソッドや、receiveData:fromPeer:inSession:context:
メソッドでデータの送受信が可能となっています。
最後に
もしかすると、音楽の再生以外にもバックグラウンド処理が許されているもの(例えばVoIPなど)でも同じことが可能かもしれません。
尚、この方法はAppleのガイドラインには書かれていないため、明らかに不推奨な方法だと思われます。
バックグラウンドで通信することに意義があるサービスのみに適応するよう心がけてください。
template engine embedded in underscore.js
クライアントサイドのテンプレートエンジンはjQueryのものが有名ですが、 今回はUnderscore.jsに備わっているミニマムなテンプレートエンジンの使い方を紹介します。 Underscore.jsは、クライアントMVCの代表格の1つであるBackbone.jsで採用されている便利ライブラリです。 (ちなみに両ライブラリの作者はCoffeeScriptを開発しているbraddunbarだったりします。)
この記事はUnderscore.js ver1.4.3を元に記載しています。
テンプレートの構文
Underscore.jsのテンプレートの区切り文字(delimiter)はデフォルトではERB-styleです。
区切り文字を変更するには、_.templateSettings
というオブジェクトの各プロパティ(evaluate
, interpolate
, escape
)を変更することで可能です。
- 評価
- コードをテンプレートの中で評価したい場合は、<% %>で括ります。
- 埋め込み
- 変数などを評価してHTMLに埋め込みたい場合、<%= %>で括ります。
- エスケープ
- HTML文字としてエスケープする場合は、<%- %>で括ります。
例)
// テンプレート実行前 <% for (var i = 0; i < 3; i++) { %> <p>Hello <%= i+1 %> Underscore!</p> <% } %> // テンプレート実行後 <p>Hello 1 Underscore!</p> <p>Hello 2 Underscore!</p> <p>Hello 3 Underscore!</p>
HTML内での使い方
実際にテンプレートを使用してみましょう。
HTML内にインラインでテンプレートを記述する場合は<script>
タグを使用し、HTMLとして画面に出力されないようにします。
そしてテンプレートの部分を文字列として取得し、それを_.template()
関数に入れ、テンプレートをプリコンパイルします。
プリコンパイルした結果は関数となっており、その関数を実行することで最終的なHTML文字列が得られます。
また、プリコンパイルした関数は引数としてオブジェクトを渡すことができ、テンプレート内ではそのプロパティ名でアクセス可能です。
元HTMLコード
<html> <head> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script> <script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.3/underscore-min.js"></script> </head> <body> <div id="target"></div> <!-- テンプレート --> <script type="text/html" id="target_template"> <% for (var i = 0; i < 3; i++) { %> <p>Hello <%= foo %>! <%= bar %>!</p> <% } %> </script> <script> // テンプレートを文字列として取得 var $templateString = $('#target_template').html(); // テンプレートをコンパイルし、テンプレート関数を得る var template = _.template($templateString); // テンプレート関数を実行 var html = template({ foo: 'foo', bar: 'bar' }); // $('#target').html(html); </script> </body> </html>
ブラウザで見るとJavaScriptが解釈され、このように表示されます。
Hello foo! bar! Hello foo! bar! Hello foo! bar!
Underscore.jsでのテンプレートの実装
使い方は非常に簡単でした。 さて、実装はどうなっているのでしょうか。 underscore.jsはコードサイズも小さくまとまっているため、実装を追いやすく、JavaScriptのコードリーディングにも最適です。
テンプレート部分のコードを転載します。
_.template = function(text, data, settings) { var render; settings = _.defaults({}, settings, _.templateSettings); // Combine delimiters into one regular expression via alternation. var matcher = new RegExp([ (settings.escape || noMatch).source, (settings.interpolate || noMatch).source, (settings.evaluate || noMatch).source ].join('|') + '|$', 'g'); // Compile the template source, escaping string literals appropriately. var index = 0; var source = "__p+='"; text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { source += text.slice(index, offset) .replace(escaper, function(match) { return '¥¥' + escapes[match]; }); if (escape) { source += "'+¥n((__t=(" + escape + "))==null?'':_.escape(__t))+¥n'"; } if (interpolate) { source += "'+¥n((__t=(" + interpolate + "))==null?'':__t)+¥n'"; } if (evaluate) { source += "';¥n" + evaluate + "¥n__p+='"; } index = offset + match.length; return match; }); source += "';¥n"; // If a variable is not specified, place data values in local scope. if (!settings.variable) source = 'with(obj||{}){¥n' + source + '}¥n'; source = "var __t,__p='',__j=Array.prototype.join," + "print=function(){__p+=__j.call(arguments,'');};¥n" + source + "return __p;¥n"; try { render = new Function(settings.variable || 'obj', '_', source); } catch (e) { e.source = source; throw e; } if (data) return render(data, _); var template = function(data) { return render.call(this, data, _); }; // Provide the compiled function source as a convenience for precompilation. template.source = 'function(' + (settings.variable || 'obj') + '){¥n' + source + '}'; return template; };
まず、matcher
という変数が定義されています。
このmatcher
はテンプレート構文に従った正規表現オブジェクトになっています。
そしてこの関数に渡されたテンプレート文字列text
を、text.replace
でテンプレート構文にマッチする箇所を探し出します。
replace
関数にfunctionを渡すと、マッチする箇所が見つかるたびにその関数が呼び出されます。ここでのreplace
関数の使い方は元のtext
を変更するのではなく、変数source
に文字列を付け足していく処理を行なっています。
replace
関数に渡されたfunction
の中は、
- escape(エスケープ)構文が見つかった場合
'+\n ( (__t=(foo) ) == null ? '' : _.escape(__t) )+\n'
- interpolate(埋め込み)構文が見つかった場合
'+\n ( (__t=(bar) ) == null ? '' : __t)+\n'
- evaluate(評価)構文が見つかった場合
';\n var i = 0; \n __p+='`
で場合分けされ、対応する文字列が変数source
に付け加えられます。
何をやっているのかよくわかりませんね。実際に実行した時の変数source
の中身を見てみましょう。
(Chromeのデバッガを使うと便利です。)
<% for (var i = 0; i < 3; i++) { %> <p>Hello <%= foo %></p> <% } %>
上記のようなテンプレート文字列があったとき、text.replace
を通過したあとの変数source
は以下のような文字列になっています。
source = "__p+='\n';for (var i = 0; i < 3; i++) { __p+='\n<p>Hello '+((__t=( foo ))==null?'':__t)+'!</p>\n';} __p+='\n';\n"
なにやら、__p
という変数に文字列を付け加えていく処理を表す文字列のようです。
最終的にこのsource
は、Function
コンストラクタを介して関数オブジェクトへと変わります。以下、動的に作られた関数renderの内容です。
function render (obj, _) { var __t, __p = '', __j = Array.prototype.join, print = function () { __p += __j.call(arguments, ''); }; with (obj || {}) { __p+='\n '; for (var i = 0; i < 3; i++) { __p+='\n <p>Hello '+ ((__t=( foo ))==null?'':__t)+ '!</p> \n '; } __p+='\n '; } return __p; }
これでやっていることは明確になりました。
__p
はテンプレート関数の実行結果であるHTML文字列を表します。
またwith文で実行時のコンテキストがobj
になるため、引数で渡されたオブジェクトのプロパティにアクセスする際に、オブジェクト名をつけずに(obj.foo)、プロパティ名そのもの(foo)でアクセスできます。
(もしオブジェクト名を付けなければいけないとすると、必ず'obj'という固定の仮引数名をテンプレートの中で使わなくてはいけなく、柔軟性に欠けます。)
この_.template()
関数の最終的な返り値は、動的に作ったrender関数を呼ぶ関数になります。
var template = function(data) { return render.call(this, data, _); }; return template;
まとめ
Underscore.jsのテンプレートエンジンの使い方と実装を見てみました。
Backbone.jsと対に使われるUnderscore.jsですが、単体で見てもこのテンプレート機能以外にも面白い(特に関数型言語のような)関数が豊富に用意されているので、皆さんも是非使って、そして実装を追ってみてください。
iPod共有アプリ「 Listen with」リリース!
新しいiPhoneアプリ「Listen with」をリリースしました!
このアプリは自分のiPhoneに入っている曲を他の人のiPhone上でも、
同時に・一緒に 聴くことができるアプリです。
Bluetoothを使ってiPhoneの曲をもう一方のiPhoneにストリーミングするため、
地下鉄などの電波のない環境でも使用できます。
アプリのデザインに関しては、このアプリのデザイナーである@Haeyoung_SさんのHPで見ることができます。
http://vividviolethy.wordpress.com/
次回から、このアプリを作る上で技術的に困難だったことを紹介したいと思います。
具体的には、
といったあたりです。
是非お友達と一緒に使ってみてください!
Listen withはApp Storeから無料でダウンロードできます。
Phrase Mania - ver2.0.0をリリース
iPhoneアプリ「Phrase Mania」を先日ver2.0.0にアップデートしました。
以前から「耳コピをしやすくしてほしい」という意見がありましたので、ver2.0.0の新機能として「耳コピモード」という新しいモードを追加しました。
耳コピモードでは
1. リピート区間を設定してその部分を繰り返し再生したり
2. 特定の秒数(1秒〜15秒)、もしくは設定したリピートの先頭場所に瞬時に巻き戻したりすることができます。
もちろん速度調節も組み合わせて使えますので、原曲の半分のテンポで同じ箇所を繰り返し再生したり、なんてことが可能です。
耳コピモードは、再生画面上部の「PHRASE・SONG・COPY」という文字をタップすることで切り替えられます。是非使ってみてください。
次のバージョンに向けてご要望などございましたら、お気軽にコメント欄からどうぞ!