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

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

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

伝えて! Palmi

Palmi ロボット

f:id:dmmlabotech:20160622194627j:plain

はい。既に挨拶のネタが切れている玄武課ロボットチームのコーイチです。

この一連のパルミー記事は、Palmi向けのアプリケーションを作成する上でのFAQ的なものになればよいな、という思いで書かれています。

前回の記事で「今回で特に紹介したいノウハウは終わりなのです」と書いた通り、インターネットへのアクセスと、 基本機能のライブラリ利用を紹介したので、私の体感としてPalmiに一般的なプログラムをするための必須ノウハウは紹介しきりました。

ですので、今回からはPalmi特有のもろもろについて紹介していきたいと思います。 (もちろん、疑問質問を頂いたら(可能な範囲で)お答えさせていただきます)

まずは、Palmiの表現です。

今回やること

Palmiは大きく分けて以下の表現でものごとを我々に伝えてくれます。

  • 喋る
  • 動く
  • 光る

それぞれ、どのような特色があるのがエンジニア目線から紹介します。

喋る

Palmiが喋るのは日本語です。

日本語であれば結構いい感じに喋ってくれます。

こんな感じに、低い声喋ったり、ゆっくり喋ったり、大きな声で喋るようなこともしてくれるのです。

youtu.be

動く

Palmiの全身は動きます。

典型的な感情は動きと音で表現してくれます。 (プリセットモーション18種のインデックスになっています)

youtu.be

光る

Palmiの顔のスティップリングレンズは光ります。

基本的にPalmiは喋って言葉を伝えてくるのですが、どうしても正確に伝えたいことは視覚に訴えるのがよいです。 (例えば、Palmiと接続するときにキーナンバーとか)

というわけで、スティップリングレンズに文字を表示してもらいました。 (ネタに困ったので、MD5ハッシュです。。。)

youtu.be

実装サンプル

Palmi独特のアウトプットの紹介となっています。

これらを組み合わせることで、より魅力的な表現になるかと思います。

喋る

発話に対する設定は発話内容の指定にタグを打つことで行います。

void SpeakTagSample::onInitialize()
{
    // Palmi -> 発話のテスト に具体的な例が記載されています。
 
    // 僕の名前は、Palmi<sapieVS_pause etime="300"/>です。
    // <sapieVS_pitch pitch="90">僕の名前は、Palmiです。</sapieVS_pitch>
    // <sapieVS_speed speed="80">僕の名前は、Palmiです。</sapieVS_speed>
    // <sapieVS_volume volume="200">僕の名前は、Palmiです。</sapieVS_volume>
 
    speak("これが標準です。");
    speak("僕の名前は、Palmiです。");
 
    speak("ポーズのテスト。");
    speak("僕の名前は、Palmi<sapieVS_pause etime=\"300\"/>です。");
 
    speak("ピッチのテスト。");
    speak("<sapieVS_pitch pitch=\"90\">僕の名前は、Palmiです。</sapieVS_pitch>");
 
    speak("スピードのテスト。");
    speak("<sapieVS_speed speed=\"80\">僕の名前は、Palmiです。</sapieVS_speed>");
 
    speak("ボリュームのテスト。");
    speak("<sapieVS_volume volume=\"200\">僕の名前は、Palmiです。</sapieVS_volume>");
 
    speak("以上です。");
 
    exitComponent();
}

動く

Palmiの開発環境としてモーションのレコーダーとエディターが提供されていますが、

正直、怠け者のエンジニアにとってモーション作成はめんどうくさい大変です。

そんなわけで、プリセットモーション(厳密にはちょっと違いますが)はとてもありがたいものなのです。

それぞれのプリセット表現が具体的にどのようなものなのか? を確認しています。

void SpcFeelingSample::onInitialize()
{
    FeelingSetting settings[] = {
        { SPC_FEELING_JOY, "喜び" },
        { SPC_FEELING_LITTLESMILE, "はにかみ" },
        { SPC_FEELING_SORROW, "悲しみ" },
        { SPC_FEELING_SORRY, "謝る" },
        { SPC_FEELING_BRAG, "自慢する" },
        { SPC_FEELING_PERPLEXITY, "困惑" },
        { SPC_FEELING_REGRET, "残念" },
        { SPC_FEELING_AGREE, "同意" },
        { SPC_FEELING_UNDERSTAND, "承知" },
        { SPC_FEELING_CONCERN, "感心" },
        { SPC_FEELING_SHY, "照れる" },
        { SPC_FEELING_SURPRISE, "驚く" },
        { SPC_FEELING_FAILURE, "失敗した" },
        { SPC_FEELING_SUCCESS, "成功した" },
        { SPC_FEELING_TELLCORRECT, "正解を伝える" },
        { SPC_FEELING_TELLINCORRECT, "失敗を伝える" },
        { SPC_FEELING_TELLATTENTION, "注意をひく" },
        { SPC_FEELING_BIGJOY, "大きな喜び" }
    };
    int length = sizeof(settings) / sizeof(FeelingSetting);
 
    speak("ボクの感情表現を順番にお見せします。");
 
    for (unsigned int i = 0; i < length; i++) {
        FeelingSetting* setting = &settings[i];
        speak(setting->feelingName + " 表現です。");
        expressFeeling(setting->feelingType);
    }
 
    speak("以上です。");
 
    // アプリケーションを終了する場合は、以下の関数「exitComponent()」を呼び出してください。
    exitComponent();
}

光る

Palmiのスティップリングレンズ(実装上の単語はLED)には、表示パターンの定義ファイル(*.led)を指定することで任意の表示を行えます。

そこで、組み込み機器向けのフォントを定義ファイル化して、文字列を指定することで順次表示するようなことを試してみました。

が、後述する問題のためにASCII文字のみ対応です。

void SpcDisplayCharaSample::onInitialize()
{
    try {
        // LED表示のためのネタとしてハッシュを得ています。
        Poco::DateTime dateTime;
        std::string dateTimeString = Poco::DateTimeFormatter::format(dateTime, "%Y%m%d%H%M");
        std::string dateTimeMessage = Poco::DateTimeFormatter::format(dateTime, "%Y年%m月%d日%H時%M分の数字部分をつないだ文字列のエムディー5ハッシュをお知らせします。");

        Poco::MD5Engine engine;
        engine.update(dateTimeString.c_str(), dateTimeString.size());
        std::string hash = Poco::DigestEngine::digestToHex(engine.digest());

        SPC_LOG_INFO(dateTimeString.c_str());
        SPC_LOG_INFO(hash.c_str());

        speak(dateTimeMessage);

        // 全文字を処理します。
        for (unsigned int i = 0; i < hash.length();) {
            // (ソースコードがUTF-8なので)UTF-8としての1文字を得ています。
            string c = utf8substr(hash, i, 1);
            i += c.length();
            SPC_LOG_INFO("i = %d, c = %s, c.length() = %d", i, c.c_str(), c.length());
            // LEDに表示した後文字を発話します。
            setCharToLed(c);
            speak(string("<sapieVS_speed speed=\"70\">"
                + c
                + "</sapieVS_speed>"
            ));
            sleep(1);
        }
    }
    catch (...) {
        speak("エラーです。");
    }

    exitComponent();
}

UTF-8 文字列をいい感じに扱う手法がイマイチ分からなかったので、 低レベルな操作処理を書いています。

// http://vivi.dyndns.org/tech/cpp/binHex.html
// ゼロパディングありの16進文字列化マクロです。
#define  hexformat(fill, wd)    std::hex<<std::setfill(fill)<<std::setw(wd)

// 文字列を16進数表現に変換します。
std::string SpcDisplayCharaSample::charToUtf8Hex(std::string& c) {
    stringstream ss;
    ss << "x";
    const char* cp = c.c_str();
    while (*cp) {
        ss << hexformat('0', 2) << ((int)*cp & 0xff);
        cp++;
    }
    SPC_LOG_INFO("charToUtf8Hex = %s", ss.str().c_str());
    return ss.str();
}

// 指定した1文字をLEDに表示します。
void SpcDisplayCharaSample::setCharToLed(std::string& c) {
    SPC_LOG_INFO("setCharToLed = %s", c.c_str());
    string dirPath;
    getDataDirPath(dirPath);
    // {UTF-8の16進表現}.led という規則でファイルを配置することで対応する文字のファイルパスを構築、表示しています。
    string filePath = dirPath + "/fonts/misaki/" + charToUtf8Hex(c) + ".led";
    long ledResult = startLED(filePath);
    SPC_LOG_INFO("setCharToLed: startLED() = %d, filePath = %s", ledResult, filePath.c_str());
}

// http://blog.sarabande.jp/post/64271702938
// UTF-8のsubstrです。
std::string SpcDisplayCharaSample::utf8substr(std::string& originalString, int offset, int length)
{
    unsigned int pos;
    unsigned char lead;
    int char_size;
    int char_count = 0;

    for (pos = offset;
        pos < originalString.size() && char_count < length;
        pos += char_size) {
        lead = originalString[pos];

        if (lead < 0x80) {
            char_size = 1;
        }
        else if (lead < 0xe0) {
            char_size = 2;
        }
        else if (lead < 0xf0) {
            char_size = 3;
        }
        else {
            char_size = 4;
        }
        char_count++;
    }

    return originalString.substr(offset, pos - offset);
}
JISコード(ISO-2022-JP)

Unicodeの世界になっても、日本で使われている文字を表現するには、JIS規格での定義が便利です。

ISO-2022-JP としてISOにも登録されている体系のうちJIS第一水準漢字、JIS第二水準漢字までの扱うのが一般的でしょうか。

これらの文字を *.led 化してプロジェクトに取り込んでみたのですけれど、6000ファイル超は現実的ではなかったようで開発機がフリーズしてしまいました。

より低レベルなLED操作APIの提供が待たれますね!!

参照リンク

Little Limit さんが公開しているフォント (美咲フォント)のパターンを光らせております。

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

github.com

まとめ

美咲フォントを *.led ファイルに変換していた時は、日本語表示してくれるぜひゃっほーい、的にテンションが高かったのですが、無念です。 LED表示はあくまで補助的なもので、Palmiは喋るのがいいのだと思います。

次回はまた別のPalmiの機能を紹介したいと思います。