2013年05月12日

Raspberry Pi+GPSで現在地図+ストリートビューを表示する車載機器を作ってみる

GWで実家に帰省してて時間もあるというわけで、Raspberry Piで、非Xで、Raspberry Piならではのアプリケーションを実現したい、と思っていたら、Raspbian "wheezy"の/opt/vc/以下にRaspberry Piのハードを活用できる各種サンプルコードが入っていました。主に、OpenMAXを叩いてどうこう、みたいなのが多いようです。

このコードをベースに、VideoCoreのハードを叩いて何か作ろう、と思いました。

さて、そのネタとして、たまたま僕が普段乗っている軽トラには非常に地図データが古いカロッツェリアのカーナビを搭載してまして、最近はスマホでナビしている関係で全然活用してませんでした。
このナビにはコンポジットビデオ入力がついているので、そこにRaspberry Piを接続し、たまたま手元にあったGPSモジュール(ストロベリー・リナックスで販売しているもの)を使用して、現在地のGoogle MapとStreet View表示するシステム MapTracer を作ってみることにしました。

/opt/vc以下にハードのデコーダーを使用してデコードのみ行なっているとおもわれるサンプルコード(hello_jpeg)があったので、これを参考にハードでコードしてフレームバッファ展開しようと思いましたが、結局ソフトデコードで全然問題なさそうでしたので、結局/opt/vc以下のコードベースのハードデコードは使ってません。


軽トラに取り付けた様子

IMG_20130504_183859resized.jpg
雨降ってます。笑
IMG_20130504_182919resized.jpg

IMG_20130504_182913resized.jpg

なんかー、若干(?)設置イメージがあやしいです。笑

MapTracerの構成
構成は以下のようにしました。
maptracer_image.png

maptracer_stack.png
GPSからのデータはRS-232Cで受信します。RS-232C-USB変換ケーブルを使用しました。Raspberry Piのピンヘッダ上のUART(/dev/ttyAMA0)でも通信出来るでしょう。
このGPSのデータ形式は多くのGPSモジュールで採用されているNMEA0183のタイプですので、オープンソースのNMEA Libraryを使用させていただき、パースすることにします。

http://nmea.sourceforge.net/

パースした結果の位置情報から、地図のデータを取得するのに、Google APIの「Static Maps API」と「Street View Image API」を使用します。Google APIは以下のサイトに入って有効にしておきます。
https://code.google.com/apis/console/

enable_api_map_and_stview.png
APIを使用したアクセスにはAPIキーが必要なのですが、それは上記Google APIのサイト内「API Access」にて取得できます。

APIのやりとりにhttpのリクエスト・レスポンスを使用します。その動作の実現にlibcurlを使用します。

GUIの描画及びキーイベント等のハンドリングにはSDL(Simple DirectMedia Layer)を使用します。
SDLの構造はこんな感じです(WikipediaのSDLライブラリの項目より)
sdl_architecture.png
SDLを使用して作成したコードは複数のプラットフォーム間で一定の互換性を持って使用できるようです。今回Linuxの非Xで構築しようとしていましたが、同じバイナリをXから起動するとマルチウィンドウでそのままうまく動作してくれました。

今回は静止画データを扱うので、SDLとその補助ライブラリであります「SDL_image」を使用しました。

実行環境の整備と、ソースダウンロード+ビルド方法

今回使用するRaspberry PiのディストリビューションはRaspbian "wheezy"の2013/2/9のもの(2013-02-09-wheezy-raspbian.zip)です。
これをRaspberry Piで起動し、SDL、libcurl等のセットアップを行っていきます。
$ sudo apt-get update
$ sudo apt-get install curl libcurl4-openssl-dev libsdl-dev
ついでに、今回使用するイーモバイルの3Gドングル D31HW用にwvdialとusb-modeswitchもインストールします。
$ sudo apt-get install usb-modeswitch
ここでudevのキックするか再起動する
$ sudo apt-get install wvdial
★ここでGPSモジュールがボードに繋がっていると、wvdialが実施する初期設定から戻ってこなくなので注意。
また、SDL_Imageの共有ライブラリのシンボリックリンクを貼っておきます(何故か張ってなかったので)。
$ cd /usr/lib/arm-linux-gnueabihf/
$ sudo ln -s libSDL_image-1.2.so.0 libSDL_image.so

MapTracerのコード(+NMEA Library)はgithubに上げてあります。
https://github.com/yishii/MapTracer

ソースコードを取得される場合には、
$ git clone https://github.com/yishii/MapTracer.git
とします。(RPiでも、gitがセットアップしてあれば(sudo apt-get install git)そのまま取得出来ます)

ここで、handle_googleapi_key.hに取得したAPI KEYを設定して下さい。

#ifndef __HANDLE_GOOGLEAPI_KEY_H__
#define __HANDLE_GOOGLEAPI_KEY_H__

#define GOOGLE_API_KEY "SET_YOUR_OWN_API_KEY_TO_HERE!" // ★ここにAPIキーをセットする!

#endif

ビルドは、
$ cd ./src
$ make
とするだけです。ビルドに成功すると、「maptracer」というプロセスが生成されています。

起動と操作方法
いくつかのオプションで動作を指定出来ます。-p オプションは必須です。
maptracer [option]
-p [tty_device] : GPSを接続したポートを指定する。例)-p /dev/ttyUSB2
-c : コンポジット出力対応に画面解像度を落とす
-d : デモモード(GPSが何を言おうと大阪駅前を指す)

また、操作ですが、キーボードで行います。
[1] : Google Mapモードへ
[2] : Google Street Viewモードへ
[0] : アプリ終了
出来れば、画面の左右にストリートビューとマップを同時表示する画面作ったほうがよかったですね。そういえば。。

IMG_20130512_202227.jpg

GPSと3Gドングルのデバイス名の固定化
今回使用したイーモバイルの3Gドングル D31HWは/dev/ttyUSB*として認識します。
GPSモジュールはUSBシリアル変換ケーブル(PL2303を使用したもの)を使用したのですが、これも/dev/ttyUSB*として認識します。
認識した順序でデバイス名が割り振られるので、番号が変化するのは固定的にデバイス名を指定して起動したい場合には不都合です。
そこで、3Gドングルは/dev/ttyUSB_3G、GPSモジュール(PL2303のUSB)は/dev/ttyUSB_GPSとして認識するようにしてみます。
ここでudevルールファイルを設定します。以下のようなファイルを用意しました。
これを例えば「/etc/udev/rules.d/45-local.rules」というファイル名として保存します。
# udev rules
ATTRS{idVendor}=="067b", ATTRS{idProduct}=="2303", NAME="ttyUSB_GPS"
SUBSYSTEM=="tty", ATTRS{idVendor}=="12d1", ATTRS{idProduct}=="143a", ATTRS{devnum}=="5", SYMLINK="ttyUSB_3G"
Raspberry Pi電源投入後自動で3G接続しアプリ起動するようにする
/etc/rc.localの最後の方に以下のように記載しました。maptracerのパス等はカスタマイズして下さい。
/usr/bin/wvdial &
sleep 20
/home/pi/MapTracer/src/maptracer -p /dev/ttyUSB_GPS


作ってみて
地図はきちんと追従してくれて楽しいです。特に、ストリートビューが楽しいかも、って思いました。ただ、自分が運転してると危なっかしくて画面見てられません。笑

助手席で見たいです。(出来れば)
また、GPSモジュールですが、今回使用したものは1Hz出力のものです。10Hz出力のものだとかなり細かく動いてもっと楽しいかもしれません。

ギモン
Raspberry PiのHDMI出力での使用時には問題ないのですが、コンポジット出力時はSDL_SetVideoMode関数の第二引数(画面の高さ)を400程度にしておかないとAPI内で落ちてしまうようでした。ひとまずコンポジット出力の時は400にするようにしています。別にコンポジット出力の解像度にバインドされている と思ってなかったのですが、そういうもんなのでしょーか?

コードの引用
bool gui_open(bool cvbs,bool no_gui)
{
    if (SDL_Init(SDL_INIT_VIDEO) < 0){
        printf("SDL_Init failed (%s)\n",SDL_GetError());
        return false;
    }
    cvbs_resolution = cvbs;
    no_gui_mode = no_gui;

    SDL_VideoInfo* vinfo = SDL_GetVideoInfo();

    if(cvbs_resolution == false){
      screen = SDL_SetVideoMode(600,
                                600,
                                16,
                                SDL_HWSURFACE);
    } else {
      screen = SDL_SetVideoMode(600,
                                400, // ★ここ
                                16,
                                SDL_HWSURFACE);
    }
GROVE - GPS
GROVE - GPS
posted with amazlet at 13.05.12
スイッチサイエンス
Raspberry Piユーザーガイド
Eben Upton Gareth Halfacree
インプレスジャパン
売り上げランキング: 1,150
Raspberry Pi Type B 512MB
Raspberry Pi Type B 512MB
posted with amazlet at 13.05.12
RS Components Ltd
売り上げランキング: 1,093
posted by いしいっち at 12:03| Comment(7) | TrackBack(0) | Raspberry Pi | このブログの読者になる | 更新情報をチェックする