無限スクロール出来るiOS用のカレンダーUIを作りました
HIScrollCalendarという無限スクロールが出来るiOS用のカレンダーUIを作りました。
もともとは個人的に作っていたアプリに使っていたものですが、そのアプリは日の目を見ずに終わってしまったのでカレンダーの部分をOSSとして公開することにしました。
インストール
CocoaPodsを使っている方はPodfileに指定して下さい。
pod 'HIScrollCalendar', :git => 'https://github.com/addsict/HIScrollCalendar.git'
アプリへの組み込み方
ヘッダ "HIScrollCalendarView.h"
を読み込み、HIScrollCalendarView
オブジェクトを作るだけで使えます。
ユーザがタップしたカレンダーの日付は - scrollCalendarView:dateDidChange:
というデリゲートメソッドで取得可能です。
#import "HIScrollCalendarView.h" @interface ViewController : UIViewController <HIScrollCalendarViewDelegate> { } @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; HIScrollCalendarView *calendarView = [[HIScrollCalendarView alloc] init]; calendarView.delegate = self; [self.view addSubview:calendarView]; } - (void)scrollCalendarView:(HIScrollCalendarView *)scrollCalendarView dateDidChange:(NSDateComponents *)dateComponent { NSString *date = [NSString stringWithFormat:@"%d/%d/%d", dateComponent.year, dateComponent.month, dateComponent.day]; NSLog(@"%@", date); // 2013/10/28 } @end
さいごに
カレンダーUIで使用している各種フォントや配色は全くconfigurableな作りになっていないので、その辺今後直していけたらなと思ってます。このカレンダーUI自体を取り込まなくても、無限スクロール実装の参考にでもしてもらえれば嬉しいです。
機能要望・バグ報告はこちらまで!
https://github.com/addsict/HIScrollCalendar
Google BigQueryのPythonライブラリbqlibを作りました
Google BigQueryをPythonから扱うbqlibというモジュールを作りました。
BigQueryとは簡単に説明すると、テラバイト級のデータセットに対してSQLを使って様々なメトリクスを分析できるサービスです。
競合にはAmazonのRedshiftやTreasureDataなどがあります。
BigQueryのPythonクライアントはGoogle社が提供しているbigquery_client.pyが既にありますが、 プリミティブな操作の集合のようなライブラリとなっており実際に使おうとすると手順が多いので、今回そのモジュールのラッパーライブラリとして簡単に扱えるものを作りました。
インストール
pipやeasy_installで入ります。依存しているbigquery_client.pyなども一緒に入ります。
$ pip install bqlib
使い方
実際にbigquery_client.pyと比較しながら使い方を説明します。
同期型クエリ
最初に同期型クエリを見てみます。
同期型クエリはクエリを開始してから実行完了までブロックされます。
(bigquery_client.pyの場合)
client = BigqueryClient(api='bigquery', api_version='2.0', project_id='my_project', credentials=credentials) job = client.RunQuery(query='SELECT date, profit FROM sales') # クエリ開始->クエリ実行完了までブロック fields, rows = client.ReadSchemaAndRows(job['configuration']['query']['destinationTable']) # 結果の取得 print rows # [['2013-10-25','12300'], ['2013-10-26','9340'], ...]
最終的にrowsがクエリの結果になります。
(bqlibの場合)
bqjob = BQJob(authorized_http, 'my_project', query='SELECT date, profit FROM sales') job_result = bqjob.run_sync() # クエリ開始->結果の取得 print job_result # [{u'date': '2013-10-25', u'profit': 12300}, {u'date': '2013-10-26', u'profit': 9340}, ...]
bqlibを使うと記述が簡単になるだけでなく、結果セットのスキーマのフィールド型から自動的にPythonに対応する型に変換してクエリの結果を返してくれるのでプログラムから扱いやすくなります。(例:'profit'フィールドはINTEGER型なので数値に変換される)
非同期型クエリ
非同期型クエリはクエリを開始した直後に制御が呼び出しプログラムに戻り、任意のタイミングで結果を取りにいくパターンです。
(bigquery_client.pyの場合)
client = BigqueryClient(api='bigquery', api_version='2.0', project_id='my_project', credentials=credentials) job_id = client.Query('SELECT date, profit FROM sales') # 非同期クエリ実行。すぐに制御が戻る #### do something #### job_reference = client.GetJobReference(job_id) job = client.WaitJob(job_reference=job_reference) # クエリ完了待ち fields, rows = client.ReadSchemaAndRows(job['configuration']['query']['destinationTable']) # 結果の取得 print rows # [['2013-10-25','12300'], ['2013-10-26','9340'], ...]
んー、これは結構めんどくさいですね...。
bqlibで書くと非同期型もすっきり書けます。
(bqlibの場合)
bqjob = BQJob(authorized_http, 'my_project', query='SELECT date, profit FROM sales') bqjob.run_async() # 非同期クエリ実行。すぐに制御が戻る #### do something #### job_result = bqjob.get_result() # 結果の取得 print job_result # [{u'date': '2013-10-25', u'profit': 12300}, {u'date': '2013-10-26', u'profit': 9340}, ...]
基本、run_syncでクエリを実行していた部分がrun_asyncに変わるだけです。
複数クエリの並列実行
ここからはbqlib独自の機能になります。
bigquery_client.pyを使って複数クエリを連続して実行しようとすると、上記の非同期型クエリをクエリ数分だけ書き、それぞれ実行し、1つ1つ実行結果を得る必要があります。
bqlibのBQJobGroupを使えば複数クエリをまとめて並列に実行可能です。
bqjob1 = BQJob(authorized_http, 'my_project', query='SELECT date, profit FROM sales') bqjob2 = BQJob(authorized_http, 'my_project', query='SELECT age FROM customer') # 実行したいjobをグループとしてまとめる job_group = BQJobGroup([bqjob1, bqjob2]) # 同期型クエリ results = job_group.run_sync() # もしくは非同期に job_group.run_async() results = job_group.get_results() print results # [[{u'date': '2013-10-25', u'profit': 12300}], [{u'age': 23}]]
自動リトライ機能
bqlibが提供するクエリ関数は全てエラーが起きた時に自動でリトライするようになっています。
ただし現時点ではBigQuery側のサーバサイドのエラー以外でも(但しデータセットやテーブルが存在しないエラーを除く)勝手にリトライしてしまうのでこの辺は改善の余地があります。
Discovery Documentの自動キャッシュ
BigQueryのAPIを叩くためにはGoogleのAPI Discovery Serviceを使用してBigQueryのAPI仕様が記述されたDiscovery Documentをダウンロードしてくる必要があります(但しbigquery_client.py v2.0.17からは何も指定しなければモジュールにビルトインされているものが使われるようになりました)。 このダウンロードされたDiscovery Documentは、ユーザがbqlibをGoogle App Engineで使用している場合memcachedに自動でキャッシュされるようになっています。
さいごに
以上bigquery_client.pyと比較しつつざっとbqlibの使い方をまとめてみました。
結局のところラッパーライブラリなので元のクライアントライブラリの変更をもろに受けてしまうのが辛いとこですが、まだまだ基本機能しかないので独自機能を入れて今後も拡張していきたいと思ってます。
要望・バグ報告はこちらまで!
https://github.com/addsict/bqlib
Mac OS Xのsayコマンドを使った簡易英語リスニング練習法
自分はいつまでたってもリスニングが苦手です。
特に数字の場合「fourty seven thousand six handred eighty nine」と発音されても
「47689」という数字がすぐには頭に思い浮かびません。
そこでMac OS Xのターミナルから使えるsayコマンドを使って数字の練習を出来るようにしました。
このようなスクリプトを組むと、1秒置きにランダムな数字(0〜32767)をMacが発音してくれます。
Practice for number in English
スピードやスピーカー(say -vで選べます)を調節することで色々な練習法が出来そうです。
BigDataダッシュボード勉強会
SIOSテクノロジーさんとTreasure Dataさん主催で、
「ビッグデータをいかに可視化するか」をテーマにした勉強会が開催されたので行ってきました。
以下気になった点をメモとして残しておきます。 間違って解釈している箇所がありましたらご指摘下さい。
Treasure Dataと連携するダッシュボードツール
- Treasure DataとMetric Insightsを組み合わせたダッシュボードの事例
- 複数プロジェクトを横断して、月単位や週単位のメトリクス、KPIを一元化して表示
- 1つのSQLを書くとそれが1つのメトリクスとして登録される
- Metric InsightsはGoogle BigQueryと組み合わせることも可能
- Treasure DataさんはBIツールとしてExcelを使えるよう開発中とのこと
- Q&A
- 「Treasure Dataを導入している企業で最も良く組み合わせて使われるBIツールはあるか?」
- 「最も使われている、というものはない。企業によって組み合わせて使われているBIツールは様々。インタラクティブな操作性ではTableauの右に出るものはないと感じているし、普及面の観点からだとExcelがやはり一番使われているBIツール。」
スモールデータ回帰分析ダッシュボード "adelie"
- adelie
- (重)回帰分析による多変量間の相関関係の計算を行い、可視化するツール
- 分析するコンサルタントと現場の隔たりが大きい現状を踏まえて、現場の人間が分析できるようにと考えられ作られた
- インラインで解析するが、気にいったモデル(メトリクス)があったら分析モデルとして保存して以後参照できる
- 技術的な面
- Python
- numpy/scipy
- Nginx
- uWSGI
- Pyramid
- c3.js
- D3.jsをラップした独自ライブラリ。対話的なチャートの制御・情報の取得が可能
- Python
Tableau Software
- Tableau Desktopのデモ
OSSを活用したダッシュボードの紹介
- Treasure Dataに蓄積しているデータを時間軸でグラフ表示するためのアプリケーションをrailsで開発
- githubに公開しているという話だったけどURLがわからない...
- Treasure Dataには、定期的に集計して結果をプッシュしてくれる機能があるらしく、TDのクレデンシャル情報をアプリ側で管理しなくて済んでいる
- この定期的に集計してプッシュしてくれる機能はBigQueryにも欲しい・・・
- GrowthForecastやKibanaを使用してダッシュボード的なものを作る話もあったり
Happy Hacking KeyboardをBluetooth化する(無改造版)
前回はHappy Hacking Keyboard(HHKB)のコントローラから自作してBluetoothキーボードに改造しました。
今回はHHKBには一切手を入れずに、HHKBをBluetooth化してみたいと思います。
※当方電子工作は初心者なため、以下の内容の正しさは保証出来ません。
また、改造に伴う機器の故障等も自己責任でお願いします。
必要なもの
- Arduino UNO
- Arduino USBホストシールド
- RN-42 HID(Bluetoothモジュール)*1
- felis/USB Host Library Rev.2.0
- addsict/USBKeyboard2BT
- 10kΩ, 5.1kΩの抵抗
- その他基板, ケーブルなど
ArduinoをUSBキーボードのホストにする
Bluetooth化の大まかな流れとしては、HHKBのUSBケーブルをArduinoに接続し、HHKBから送られてくる信号をArduinoがBluetoothに変換してPCに送ります。 Arduinoが一旦PC代わりとなり、本来ならPCが受け取る信号をArduinoを通して送るイメージです。 USBデバイスを操作する側をホストと呼びますが、つまりArduinoがUSBキーボードのホストになればいいわけです。
ArduinoのUSBホスト化はUSBホストシールドとそれに対応したライブラリ USB Host Shield 2.0を使います。 このライブラリにはUSBのHIDデバイスに対応したクラスが用意されており(hidboot.(cpp|h))、その中のKeyboardReportParserクラスをサブクラス化して使います。
HHKB側からUSB経由でHIDレポートが送られてくるとKeyboardReportParser::Parseメソッドが呼び出されます。そのParseメソッドをオーバーライドすれば、送られてきたHIDレポートを自由に扱えます。
今回はそのHIDレポートをBluetoothモジュールに素通りさせればokです。
HIDレポートについては前回のものを参考にしてくだい。
class HIDKeyboardParser : public KeyboardReportParser { virtual void Parse(HID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf); }; void HIDKeyboardParser::Parse(HID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf) { // bufにはHHKBから送られてきたHIDレポートが入っているので、 // そのレポートをそのままBluetoothモジュールに対してシリアルで送信 sendKeyCodesBySerial(buf[0], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7]); } void sendKeyCodesBySerial(uint8_t modifiers, uint8_t keycode0, uint8_t keycode1, uint8_t keycode2, uint8_t keycode3, uint8_t keycode4, uint8_t keycode5) { Serial.write(0xFD); // Raw Report Mode Serial.write(0x09); // Length Serial.write(0x01); // Descriptor 0x01=Keyboard /* send key codes(8 bytes all) */ Serial.write(modifiers); // modifier keys Serial.write(0x00, 1); // reserved Serial.write(keycode0); // keycode0 Serial.write(keycode1); // keycode1 Serial.write(keycode2); // keycode2 Serial.write(keycode3); // keycode3 Serial.write(keycode4); // keycode4 Serial.write(keycode5); // keycode5 delay(5); }
全体のスケッチはaddsict/USBKeyboard2BTにあります。 このスケッチをArduinoに転送し、ArduinoとBluetoothモジュールをシリアルで接続するだけでBluetooth化の完了です。
これでHHKBに限らずUSBキーボード(HID)ならどんなものでも一瞬でBluetoothキーボードにすることが出来ます。 あとはPICやAVRマイコンなどを使用して小型化すれば実用的になりそうです。
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' }, };