読者です 読者をやめる 読者になる 読者になる

mbedからニコニコ新検索βAPIにアクセスして結果を表示

mbed 電子工作 IoT プログラミング

この記事は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用イーサネット接続キット - スイッチサイエンスを使いました。

前回、ネットワークの接続がうまくいかないことがあったのですが、キットの3.3VとGNDをmbedに接続し忘れていたことが原因でした・・・

ちゃんと接続すると安定して動作するようになりました。

1. mbedからニコニコ新検索βAPIへアクセスする

実際のコードは mbed_nicovideo_search_api - a mercurial repository | 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さんです。よろしくお願いします。


関連エントリ