この記事はmbed Advent Calendar 2014 - Adventarの1日目の記事です。
以前、mbed祭り2014@夏の東銀座でmbedからインターネット上のAPIへアクセスしてデータを表示というプレゼンをやりました。
そのときはニコニコ新検索βAPIからデータを取得する際に、herokuを経由して文字コードを変換&GETメソッドでアクセスできるようにして、OLEDに表示していました。
その後、使用したGraphicOLEDのライブラリがアッフデートされてUTF-8に対応していたので、今回はmbedから直接ニコニコ新検索βAPIへアクセスして表示してみます。
POSTメソッドでAPIにアクセスする参考になるかと思います。
最後はこんな感じになります。
0. mbedと有機ELモジュールおよびイーサネットコネクタの接続
有機ELモジュールについてはmbedからheroku上のデータを取得してOLEDに表示してみる - 工作とかプログラミングとかの記事と同じ接続なので、 そちらを参照ください。
前回と同じくイーサネット接続にはmbed LPC1768用イーサネット接続キット - スイッチサイエンスを使いました。
前回、ネットワークの接続がうまくいかないことがあったのですが、キットの3.3VとGNDをmbedに接続し忘れていたことが原因でした・・・
ちゃんと接続すると安定して動作するようになりました。
1. mbedからニコニコ新検索βAPIへアクセスする
実際のコードは mbed_nicovideo_search_api - ニコニコ新検索βAPIからデータを取得して有機ELに表示する。 | Mbed に公開しています。
コードの解説をしていきます。
コードはすべてmain.cpp
に書いてあるので、その解説になります。
1.1 イーサネットコネクタのLEDを光らせる
イーサネットコネクタには通信状態を示すLEDがついています。 そのLEDを光らせるコードです。
こちらのコードは mbed用イーサネット接続キットでメール送信を試す。: じぇーけーそふとのこーなー を使わせていただきました。ありがとうございます。
17-26行目:
// ethernet DigitalIn lnk(P1_25); DigitalIn spd(P1_26); DigitalOut speed(p29); DigitalOut link(p30); void flip(void const *args) { speed = !spd; link = !lnk; }
35-37行目:
// ethernet led RtosTimer flipper(flip, osTimerPeriodic, NULL); flipper.start(50);
コードとしてはネットワークの接続状態が出力されているP1_25
, P1_26
のピンを50msごとにLEDの接続されているp29
,p30
へリダイレクトするようになっています。
1.2 mbedからニコニコ新検索βAPIへアクセスする
53-55行目:
char post_data[256]; sprintf(post_data, "{\"query\":\"%s\",\"service\":[\"video\"],\"search\":[\"tags\"],\"join\":[\"cmsid\",\"title\",\"start_time\"],\"from\":0,\"size\":1,\"sort_by\":\"start_time\",\"issuer\":\"apiguide\",\"reason\":\"ma10\"}", SEARCH_TAG.c_str()); pc.printf("POST Data:\r\n%s\r\nLength:%d\r\n", post_data, strlen(post_data));
post_data
にはJSON形式の検索クエリが入っています。検索クエリのフォーマットについてはAPIリファレンスに書いてあるので、そちらを参照ください。
内容としてはmbed
タグのついた動画を新着順にソートして新しいものから1件取得するというものになっています。
57-60行目:
char http_cmd[1024]; sprintf(http_cmd, "POST %s HTTP/1.1\r\nHost: %s:%d\r\nAccept: */*\r\nContent-Length: %d\r\nContent-Type: application/json\r\n\r\n%s\r\n\r\n", API_PATH.c_str(), API_HOST.c_str(), API_PORT, strlen(post_data), post_data); pc.printf("Request:\r\n%s\r\n", http_cmd); sock.send_all(http_cmd, sizeof(http_cmd)-1);
HTTPメソッドにPOSTを指定してリクエストを送る部分です。
POSTでは送るデータの長さをContent-Length
ヘッダーに指定する必要があります。
また送るデータの形式Content-Type
を指定する必要があります。今回はJSON形式なのでapplication/json
を指定しています。
key=value
みたいな値(URLエンコード済み)とキーを指定する形式であれば、application/x-www-form-urlencoded
を指定します。
76行目:
response_body = response.substr((int)response.find("\r\n\r\n") + 1);
APIから受け取ったレスポンスからヘッダー部を取り除く処理です。
ヘッダーとボディは改行コード2つで分かれており、その部分で文字列を切り取っています。
1.3 レスポンスから動画タイトルの抽出
APIのレスポンスボディは以下のように4行のJSONから構成されています。
{"dqnid":"9c55fb01-42db-48c1-8ef5-8268b9fc04c7","type":"stats","values":[{"_rowid":0,"service":"video","total":52}]} {"dqnid":"9c55fb01-42db-48c1-8ef5-8268b9fc04c7","endofstream":true,"type":"stats"} {"dqnid":"9c55fb01-42db-48c1-8ef5-8268b9fc04c7","type":"hits","values":[{"_rowid":0,"cmsid":"sm24696784","start_time":"2014-10-15 12:54:14","title":"mbedプラットフォーム「うおーるぼっとBLE」"}]} {"dqnid":"9c55fb01-42db-48c1-8ef5-8268b9fc04c7","endofstream":true,"type":"hits"}
3行目のvalues
の中に動画タイトルが含まれいるので、まず3行目以降を取り出します。
81-83行目:
for(int i=0; i<3;i++){ response_body = response_body.substr((int)response_body.find("\n{") + 1); }
その後、values内の[
と]
で挟まれた部分を抽出します。
85-87行目:
int start_pos = (int)response_body.find("[") + 1; int end_pos = (int)response_body.find("]"); response_body = response_body.substr(start_pos, end_pos - start_pos);
- (注): 動画タイトルなどの文字列に
]
が含まれているとうまくいかないので、その時はエスケープをちゃんと見るようにしないといけないです。
ここまでで以下のように動画のタイトルが含まれたJSON形式の文字列を抽出できます。
{"_rowid":0,"cmsid":"sm24696784","start_time":"2014-10-15 12:54:14","title":"mbedプラットフォーム「 うおーるぼっとBLE」"}
1.4 JSON形式の文字列のパース
あとはpicojsonというJSONパーサーを使って、title
の部分を抜き出して表示するだけです。
92-100行目:
picojson::value v; const char *json = response_body.c_str(); string err = picojson::parse(v, json, json + strlen(json)); pc.printf("--> error %s\r\n", err.c_str()); pc.printf("--> values %s\r\n", v.get("title").get<string>().c_str()); oled.cls(); oled.printf(v.get("title").get<string>().c_str());
picojson::parse
メソッドでJSON形式の文字列をパースできます。
その中から特定のキー、例えばtitle
の値を取りたいときはv.get("title")
のように指定し、さらにメソッドをつなげてv.get("title").get<string>()
のようにすると、title
の文字列(string型)を取得できます。
- (注): 3行目をそのまま
picojson
で読み込ませても動く気がするのですが、うまくいかなかったのでvalues
部分だけ抽出してpicojson
でパースしています。
あとはOLEDに出力してあげれば最初の画像のように文字列を表示することができます。
まだ12月5日、7日、10日以降が空いているので、 mbed Advent Calendar 2014 - Adventarにぜひ参加してください。複数書いてもいいですよ!
12月2日は@en129さんです。よろしくお願いします。