2011年9月18日日曜日

伊丹スカイパークで飛行機三昧

大阪国際空港(伊丹空港)の脇にある、「伊丹スカイパーク」に遊びに行ってきました。

伊丹スカイパークは、もともと空港周辺地域への航空機騒音軽減のために作られたため、ビルの3階建てくらいの高さの築堤が1km以上も続く、かなり大きな公園です。
位置的には、空港の管制塔から滑走路をはさんでちょうど真向かいにあたる位置にあり、飛行機の眺めは抜群です。

さらに、伊丹スカイパークの隣には、原田下水処理場があり、その屋上が緑化されており「スカイランドHARADA」として一般公開されています。こちらの方がさらに高い位置にあるため、空港全体を俯瞰するような形になり、またひと味違った風景が楽しめます。航空無線を聞きながら眺めると、気分は航空管制官です(^^;

どちらの公園も、とにかくすぐ近くを飛行機が離着陸するため、かなりの迫力です。飛行機が好きじゃない人でも、大興奮するんじゃないかと思います。
コンパクトデジカメで手持ち撮影したので、あまり良い写真は撮れてませんが、その中から厳選したのを以下に数枚ほど。

航空機の撮影は、望遠性能の良さがモノを言う世界です。会社ではもともと写真部に所属していたにもかかわらず、「重い」という理由でデジタル一眼を手放した私ですが、こういう時は一眼にデッカいレンズを着けて撮影したくなります。というか、周りで写真撮ってる人は、そういうカメラを持ってる人達ばかりでした。

園内の管理棟の中には、退役したレーダー表示機が展示されていました。

レーダー表示機には、子供向けのゲーム画面が表示されていました。(本物のレーダー表示ってどんな物なのかなぁ、と思いました。)

公園のすぐ脇には、本物のレーダーのアンテナがありました。アンテナ設備には何かと反応しちゃう私です。

そんなわけで、とても楽しい公園でした。飛行機好きには特におススメしたい場所です。

2011年9月11日日曜日

ネットワークフォトフレームの製作

[2012/8/25追記]本記事の内容は古くなっています。
最新の情報は mpod mother board をご覧ください。

概要
USBメモリのインタフェースを備えるディジタルフォトフレームを、フォトフレーム側への改造をせずにネットワーク対応させます。
フォトフレームの電源を投入しスイッチを押すと、Picasaウェブアルバムから写真を取って来て、自動で表示を始めます。


概念図
このような目的に使える、ネットワークとUSBの両方を備える便利なデバイス、と言えば、もちろんmbedでしょう。
ネットワークは、パルストランス付きのRJ45コネクタをmbedに繋ぐだけ。
USBメモリをUSBスイッチ(FSUSB30MUX:フェアチャイルドセミコンダクタ)を介してmbedとフォトフレームに接続し、mbedからの制御信号によりUSBメモリの接続先を「mbedのUSBホスト」と「フォトフレーム」とに切り替えて使用します。
最初は、mbed自身のファーム格納領域であるUSB mass strageを間借りして写真も保存しようと思いましたが、容量が約2MBと小さく、たくさんの写真を保存するには適していないため、このような構成にしています。


この構成では、フォトフレーム側からは本機はただのUSBメモリとして見えるため、フォトフレームには一切手を加えずに済むのが特徴です。USBスイッチがmbed側に倒れている時は、フォトフレームからUSBメモリが抜き取られたのと同じ状態です。


外観


使い方
まず、本機のUSBコネクタをパソコンにつなげます。
USBメモリの中身が見えるので、トップのフォルダに"netusbm.cfg"というテキストファイルを作成し、自分のPicasaウェブアルバムの特定のアルバムのURLを記述します。
http://picasaweb.google.com/********/********?authkey=***********#
本機にダウンロードさせたい写真は、ここで指定したアルバムに保存しておくようにします。
また、同じフォルダに"DCIM"という空のフォルダも作っておきます。
フォトフレームは、この"DCIM"フォルダの下にある画像を探すので、本機はダウンロードした画像をこのフォルダに保存します。


ここまで準備ができたら、EtherNetケーブルを本機に接続し、パソコンからUSBコネクタを取り外し、そのUSBケーブルをフォトフレームに繋げます。
フォトフレームの電源を入れると、本機の中のUSBメモリがマウントされ、画像を読み込もうとしますが、最初は画像が無いので何も表示されないか、エラー画面などが出ると思います。
ここで、本機の上面にあるスイッチを押すと、フォトフレームからUSBメモリがアンマウントされ、mbed側に接続されます。mbedはEtherNet経由で画像を収集し、完了したらUSBメモリを再びフォトフレーム側に接続します。フォトフレーム側はUSBメモリの接続を検知し、保存されている画像を表示し始めるはずです。


部品表
    項目メーカ型番価格
    (円)
    数量備考
    ディジタル
    フォトフレーム
    kodakEasyShare P72501
    会社の新年会ビンゴ景品
    USBメモリBuffaloRUF2PS4GBK20001
    作品全体のサイズを小さくするため、
    小型のUSBメモリを選択した
    コントローラNXPmbed NXP LPC176860001
    USBスイッチFairchildFSUSB30MUX1601
    Digi-Keyで購入したため
    2000円の送料が必要
    RJ45コネクタ3601
    USBコネクタBメス
    (receptacle)
    1601
    フォトフレーム接続用
    USBコネクタAメス
    (receptacle)
    1301
    USBメモリ接続用
    ケーステイシン電機TB-51B1201
    D40×W70×H25

    回路図

    ケース加工
    mbedとUSBコネクタ、RJ45コネクタ、USBメモリを入れるため、かなり窮屈です。
    もともとmbedについているUSBコネクタとリセットスイッチがケースと干渉するので、写真のように内部を彫刻刀で削りました。
    また、スイッチを外に出すための穴を開けています。

    回路実装
    mbed用のコネクタの間にUSBメモリを配置し、さらに基板を切り欠いてUSB-BコネクタとRJ45コネクタをホットメルトで固定しています。
    USBメモリ用のUSB-Aコネクタの配置が窮屈で、USBメモリが取り外し不可能になってしまいました。USBメモリは約2000円と高価ですが、仕方なく本機専用としました。


    ソフトウェア
    以下に示すのが、私が書いた部分です。
    これ以外に、以下のライブラリも利用しています。
    ・EtherNetIf
    ・FATFileSystem
    ・HTTPClient
    ・MSCUsbHost
    なお、動作を優先させたため、リファクタリングの余地が多分に残っていますので、その点ご了承ください。

    main.cpp
    #include "mbed.h"
    #include "MSCFileSystem.h"
    #include "EthernetNetIf.h"
    #include "HTTPClient.h"
    #include "picasaUrlParser.h"
    
    #define URL_BUF_SIZE (100)
    
    DigitalOut led(LED1);
    DigitalInOut usbsel(p8);
    DigitalIn startsw(p9);
    
    EthernetNetIf eth;
    HTTPClient http;
    
    HTTPResult result;
    bool completed = false;
    
    void
    request_callback(HTTPResult r) {
        result = r;
        completed = true;
    }
    
    int
    main(void) {
    
        printf("EtherIF Setting up...\n");
        EthernetErr ethErr = eth.setup();
        if (ethErr) {
            printf("Error %d in setup.\n", ethErr);
            return -1;
        }
        printf("EtherIF Setup OK\n\n");
    
        while (1) {
            usbsel.output();
            usbsel = 1;
            led = !led;
            completed = false;
    
            while (1) {
                if (startsw == 0) {
                    break;
                }
            }
            usbsel = 0;
            led = !led;
            usbsel.input();
    
            {
                char url[URL_BUF_SIZE];
                
                // Mount flash drive under the name "msc"
                MSCFileSystem msc = MSCFileSystem("msc");
    
                // Read the target URL from configuration file.
                FILE *fpcfg = fopen("/msc/netusbm.cfg", "r");
                fgets(url, URL_BUF_SIZE, fpcfg);
                fclose(fpcfg);
                url[strlen(url)-1] = '\0';
    
                // Create parser
                FILE *fplist = fopen("/msc/url_list.txt", "w");
                picasaUrlParser* parser = picasaUrlParser_create(fplist);
    
                HTTPStream stream;
    
                char BigBuf[512 + 1] = {0};
                stream.readNext((byte*)BigBuf, 512);
    
                printf("%s\n", url);
                HTTPResult r = http.get(url, &stream, request_callback);
    
                while (!completed) {
                    Net::poll(); //Polls the Networking stack
                    if (stream.readable()) {
                        BigBuf[stream.readLen()] = 0;
    
                        int i = 0;
                        while (0 != BigBuf[i]) {
                            picasaUrlParser_setChar(parser, BigBuf[i]);
                            i++;
                        }
                        stream.readNext((byte*)BigBuf, 512);
                    }
                }
                printf("--------------\n");
                if (result == HTTP_OK) {
                    printf("Read completely\n\n");
                } else {
                    printf("Error %d\n\n", result);
                }
    
                // delete parser
                picasaUrlParser_destroy(parser);
                fclose(fplist);
    
                fplist = fopen("/msc/url_list.txt", "r");
                if (fplist == NULL) {
                    error("Could not open file for read\n");
                }
                int j=0;
                char target[128];
                while (NULL != fgets(target, 128, fplist)) {
                    char* tmp = target;
                    while ('\n' != *tmp) {
                        tmp++;
                    }
                    *tmp = '\0';
    
                    printf("%s\n", target);
    
                    char filename[64];
                    sprintf(filename, "/msc/DCIM/img%03d.jpg", j);
                    printf("filename : %s\n", filename);
                    HTTPFile fpimg = HTTPFile (filename);
    
                    // Request a page and store it into a file.
                    HTTPResult r2 = http.get(target, &fpimg);
    
                    if (r2 == HTTP_OK) {
                        printf("Read completely\n\n");
                    } else {
                        printf("Error %d\n\n", r2);
                    }
    
                    // Close the file.
                    fpimg.clear();
                    j++;
                }
                fclose(fplist);
    
                // Work is done!
            }
        }
        return 0;
    }
    

    Picasaから送られるHTMLから、画像ファイルのURLだけを抜き出すため、ステートマシンによる簡単なパーサを作りました。
    URLの部分は、以下の様な特徴的な文字列が並びます。
    [{"url":"http://lh5.ggpht.com/(中略)/IMGP0486.JPG","height":1200,"width":1600,"type":"image/jpeg"}]
    
    こういった文字列を含むファイル全体を、1文字ずつパーサに流し込んで行き、あるところで
    [{"url":"
    
    を検出したらURLの読み取りを開始し、次に
    "
    
    を検出したらURLの終了だと判断します。
    厳密には検出のための条件をもっと増やした方が良いかもしれませんが、個人で使う分にはこれで十分です。当然のことながら、Googleが仕様を変更したら、パーサも設計し直す必要があります。
    今回はパーサを独自に作りましたが、もし汎用の正規表現ライブラリで良いのがあれば、それを使った方が良いでしょう。私はC言語で使える正規表現ライブラリをひとつ評価しましたが、使い勝手が自分の要求には合わなかったので、自作しました。

    picasaUrlParser.h
    #ifndef PICASAURLPARSER_HEADER_DEFINED
    #define PICASAURLPARSER_HEADER_DEFINED
    
    #include "FATFileSystem.h"
    
    #define MAX_URL_LEN (128)
    
    typedef enum {
        initalized,
        judge1,
        judge2,
        judge3,
        judge4,
        judge5,
        judge6,
        judge7,
        judge8,
        parsing
    }picasaUrlParserState;
    
    typedef struct {
        int     iPos;
        char    url[MAX_URL_LEN];
        picasaUrlParserState    state;
        FILE*   fpurl;
    }picasaUrlParser;
    
    picasaUrlParser*
    picasaUrlParser_create(FILE* fp);
    
    void
    picasaUrlParser_destroy(picasaUrlParser* pThis);
    
    void
    picasaUrlParser_setChar(picasaUrlParser* pThis, char target);
    
    #endif /* PICASAURLPARSER_HEADER_DEFINED */
    
    picasaUrlParser.cpp
    #include <stdio.h>
    #include <stdlib.h>
    #include "picasaUrlParser.h"
    
    picasaUrlParser*
    picasaUrlParser_create(FILE* fp) {
        picasaUrlParser* pThis;
    
        pThis = (picasaUrlParser*)malloc(sizeof(picasaUrlParser));
        if (NULL == pThis) {
            printf("Memory Allocation Error.\n");
            while (1) {
                //Do nothing
            }
        }
    
        pThis->iPos = 0;
        pThis->state = initalized;
        pThis->fpurl = fp;
    
        return pThis;
    }
    
    void
    picasaUrlParser_destroy(picasaUrlParser* pThis) {
        if (NULL == pThis) {
            while (1) {
                //Do nothing
            }
        }
    
        free(pThis);
        return;
    }
    
    void
    picasaUrlParser_setChar(picasaUrlParser* pThis, char target) {
        if (NULL == pThis) {
            while (1) {
                //Do nothing
            }
        }
        switch (pThis->state) {
            case initalized:
                if ('[' == target) {
                    pThis->state = judge1;
                }
                break;
    
            case judge1:
                if ('{' == target) {
                    pThis->state = judge2;
                } else {
                    pThis->state = initalized;
                }
                break;
    
            case judge2:
                if ('"' == target) {
                    pThis->state = judge3;
                } else {
                    pThis->state = initalized;
                }
                break;
    
            case judge3:
                if ('u' == target) {
                    pThis->state = judge4;
                } else {
                    pThis->state = initalized;
                }
                break;
    
            case judge4:
                if ('r' == target) {
                    pThis->state = judge5;
                } else {
                    pThis->state = initalized;
                }
                break;
    
            case judge5:
                if ('l' == target) {
                    pThis->state = judge6;
                } else {
                    pThis->state = initalized;
                }
                break;
    
            case judge6:
                if ('"' == target) {
                    pThis->state = judge7;
                } else {
                    pThis->state = initalized;
                }
                break;
    
            case judge7:
                if (':' == target) {
                    pThis->state = judge8;
                } else {
                    pThis->state = initalized;
                }
                break;
    
            case judge8:
                if ('"' == target) {
                    pThis->state = parsing;
                } else {
                    pThis->state = initalized;
                }
                break;
    
            case parsing:
                if ('"' != target) {
                    pThis->url[pThis->iPos] = target;
                    (pThis->iPos)++;
                } else {
                    pThis->url[pThis->iPos] = '\0';
                    fprintf(pThis->fpurl, "%s\n", pThis->url);
                    pThis->iPos=0;
                    pThis->state = initalized;
                }
                break;
    
            default:
                break;
        }
    
        return;
    }
    

    まとめ
    mbedを使ってディジタルフォトフレームに手を加えずにネットワーク対応しました。
    このような用途にmbedはうってつけです。HTTP、USBホストなどの有用なライブラリが揃っているからです。
    今回、これらのライブラリが無ければ、ここまで辿り着けなかったことでしょう。
    ライブラリを提供してくださった皆様には、大変感謝しております。ありがとうございました。

    製作してから、この文章を書くまで、半年以上が経過してしまいました。この点は反省。
    次回からは、作ったらすぐにドキュメントにまとめるようにしたいと思います。

    お約束
    このドキュメントに書かれている内容について、正確になるよう筆者は努力しますが、保証はできません。
    もし、間違いなどを発見されましたら、ご指摘頂ければ幸いです。

    このドキュメントに起因するいかなる損害についても、筆者は一切の責任を負いません。
    ご利用になる場合は、各位にて十分な検証を行うようにお願いします。

    また、本ドキュメントに関する著作権は筆者に帰属するものとします。
    ただし、ドキュメントの内容は自由に複製したり、改変して使用して頂いても構いません。