DMM.comラボエンジニアブログ

DMM.comラボのエンジニアブログです。DMM.comを支える技術について書いています。

DMM insideに引っ越しました。 移転先はこちら -> https://inside.dmm.com/

Palmi 設定ファイルを読んで!

f:id:dmmlabotech:20160622194627j:plain

どうも、パルミーに関するアプリケーション開発の啓蒙を画策する、玄武課ロボットチームのコーイチです。

さて、Palmiはロボットなわけですが、ロボットが理解するのは基本的には日本語ではなくプログラム言語です。

プログラム言語は数多くありますが、Palmiが理解するのはC++です。

C++では言語の基本機能だけを使っていると、何をやるにも非常に大量のコードを書く必要が出てきます。

そんな状況下で、Palmiにファイルを読んでもらうにはどうすればよいのでしょうか?

前回、電車遅延を教えてもらった際にはサラッと流してしまいましたが、Palmiには様々なライブラリがインストール済です。 今回はそれらのライブラリを使用して楽勝でファイルを読んでもらいます。

今回やること

古今東西、何かしらのアプリケーションを開発するにあたって、ファイルを読み込むという動作は極一般的です。

二回目である今回は、初回よりもさらにさかのぼってこれを題材としました。

PalmiにXMLファイルの内容を読み上げてもらいました。 頭をなでるたびに、次の要素を読んでくれます。

youtu.be

実装サンプル

PalmiはmicroSDカードへの入出力を行うことができます。

ここでは、あらかじめ配置しておいたXMLファイルを設定ファイルとして読み込んでメモリに保持、 頭をなでるイベント毎に発話させるようにしています。

// ヘッダー抜粋
class SpcPocoConfigurationSample : spc::SPCBase
{
private:
    // 設定ファイルの読み込み結果を保持する変数群です。
    std::string singleValue;
    std::string nestedValue;
    std::vector<std::string> messages;
    // 今処理しているメッセージのインデックスです。
    int currentMessageIndex;
// ~略~
// 実装抜粋
#include "spc/spcbase2.h"
#include "SpcBaseCode.h"

#include <sstream>
#include <Poco/Exception.h>
#include <Poco/Util/XMLConfiguration.h>
#include <Poco/Util/AbstractConfiguration.h>

using namespace std;
using namespace spc;
using namespace Poco;
using namespace Poco::Util;

/**
* @brief アプリケーション初期化イベント。
*
* アプリケーションの実行開始時に呼び出されます。
*/
void SpcPocoConfigurationSample::onInitialize()
{
    SPC_LOG_INFO("onInitialize");
    // 頭をタッチした際にアプリを終了せず、イベントの通知を行うように設定します。
    setActionPOTFluctuation(SPC_ONLY_NOTYFY_POT_FLUCTUATION);

    try {
        // microSDカードが挿入されている場合はそちらのファイルを参照するようにしています。
        // SPC毎に読み書きできるディレクトリが異なります。
        // それらは所定のメソッドで取得します。
        bool sdCardEnable = mountMicroSD() == 0;
        std::string configDirPath;
        std::string configFilePath;
        if (sdCardEnable) {
            // SDカード上のSPC向けディレクトリは「/_PALMI_/{SPC名}」です。
            // あらかじめファイルを配置しておくようなことが可能です。
            // このサンプルでは「/_PALMI_/SpcPocoConfigurationSample」に「config.xml」を配置することで読み込まれます。
            getMicroSDDirPath(configDirPath);
        }
        else {
            getDataDirPath(configDirPath);
        }
        configFilePath = configDirPath + "/config.xml";

        // ここでは XML 形式の設定ファイルを読み込むクラスを用います。
        // 他にも各種形式が AbstractConfiguration を実装しています。
        // http://www.appinf.com/docs/poco/Poco.Util.XMLConfiguration.html
        // http://www.appinf.com/docs/poco/Poco.Util.AbstractConfiguration.html
        // インスタンス化するだけで読み込みが終わります。
        AutoPtr<AbstractConfiguration> settings(new XMLConfiguration(configFilePath));

        // 読み込み結果の取得方法は AbstractConfiguration として標準化されています。
        singleValue = settings->getString("single", "default-single-value");
        SPC_LOG_INFO(singleValue.c_str());
        speak(singleValue);

        // ネストした値は要素名の「.」区切りで取得できます。
        nestedValue = settings->getString("parent.child");
        SPC_LOG_INFO(nestedValue.c_str());
        speak(nestedValue);

        // 同一の要素名が複数ある場合は、配列の添え字のように要素を指定します。
        stringstream ss;
        int i;
        i = 0;
        while (true) {
            ss.str("");
            ss << "message" << "[" << i << "]";
            if (!settings->hasProperty(ss.str())) {
                break;
            }
            string message = settings->getString(ss.str());
            messages.push_back(message);
            i++;
        }
        ss.str("");
        ss << i << "個のメッセージを読み込みました。";
        speak(ss.str());
    }
    catch (Poco::Exception& ex) {
        SPC_LOG_ERROR(ex.displayText().c_str());
        speak(ex.displayText().c_str());
    }
    catch (...) {
        SPC_LOG_ERROR("handle error.");
        speak("handle error.");
    }
    umountMicroSD();

    currentMessageIndex = -1;
}

 
/**
* @brief POTセンサーイベント
*
* Palmiの頭部をさわった時に呼び出されます。
*/
void SpcPocoConfigurationSample::onPOTFluctuationCatch()
{
    SPC_LOG_INFO("onPOTFluctuationCatch");
    if (messages.size() == 0) {
        speak("メッセージがありません。");
        return;
    }
    // 読み込んだ要素の数だけ順番に発話させます。
    currentMessageIndex++;
    if ((long)messages.size() <= currentMessageIndex) {
        speak("先頭に戻ります。");
        currentMessageIndex = 0;
    }
    speak(messages[currentMessageIndex]);
}

基本機能ライブラリと参照設定

ライブラリには様々なものがあります(別途一覧化してお知らせしたいところです)が、特に重要なのが基本機能を提供するものです。

少々古い例えですが、JavaでいうところのCommonsのような位置づけです。 自分で実装すれば何とかなることだけれど、使えば手間を省けてとっても便利、というものですね。

開発者向けのサイトでも案内されていますが、それらのライブラリとして libxml2 や POCO C++ Libraries がインストールされています。

使用するには以下のようなプロジェクト設定が必要となります。

<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
<!-- 略 -->
    <IncludePath>
    </IncludePath>
    <CompileDef>
    </CompileDef>
    <LinkFlags>
    </LinkFlags>
    <LinkPath>
    </LinkPath>
    <LinkAddLib>-lPocoFoundation -lPocoUtil</LinkAddLib>
<!-- 略 -->
  </PropertyGroup>
<!-- 略 -->

他の POCO 提供クラス(バージョンは1.4系)も同様にリンク指定することで使用できます。

尚、この設定だとクロスコンパイル周りの警告が出ます。 美しい設定ノウハウをお持ちの方、ご教授いただけましたらありがたいです。

余談としてC++のバージョン

PalmiはUbuntsu12のLTSで動作しています。

この時期のg++(コンパイラー)がサポートするC++のバージョンはC++11に一歩届きません。

ですので、C++11で採用されたラムダや型推論は使用できないのです。 この辺りを踏まえてコーディングしないと、コンパイルが通らなくて「あるぇ?」となったりするので注意が必要です。

はい、そうです。経験談です。

参照リンク

ソースコードはGitHubで公開しています。

github.com

今回使用した機能はこちら。

まとめ

今回は Palmi に設定ファイルを読んでもらいました。

インターネットアクセスと、便利ライブラリの使用さえできれば、やりたいことは大体できると思います。 ですので、今回で特に紹介したいノウハウは終わりなのです。

ただ、内々に「連載するよ!」と宣言してしまったので、何とかネタをひねり出して続けます。 次回はPalmiが持つ基本的なアウトプット表現を紹介する予定です。

それではまた会う日まで。

おまけ

身内から要望が上がってきたので Issue に転記してやりました。

github.com

Palmi はちょっとした設定なら Fwapper というユーティリティソフトから設定できます。 詳しくはAPI仕様やユーザーガイドを参照下さい。 ぞれぞれ開発者向けサイトで公開されています。

palmigarden.net