MESHをTHETA Sのリモートシャッターにしてみた

  • 15
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

この記事はRICOH THETA Advent Calendar 2015 21日目の記事です。
MESHを使ってThetaのシャッターをリモートで切ることができるソフトウェアタグを作ります。
なお、THETA API v2 を利用するため現在対応している機種はTheta Sのみとなります。

MESHって?

これ https://meshprj.com/
BluetoothでiOSデバイスとペアリングして使えるワイヤレス電子ブロック的なものです。
MESH

Move(動き)タグ、Button(ボタン)タグ等のハードウェアタグとソフトウェアタグを組み合わせたレシピを定義することができます。

Recipe
先日SDK(ベータ版)が公開され、jsベースでのソフトウェアタグの開発が可能となりました。
これにより、オンライン開発環境でコードを書いて、自分の端末にダウンロードして使うことができるようになります。
また、iPhone版のアプリも提供されたので、iPadがなくても使えるようになり、より手軽に遊べるようになっています。

ソフトウェアタグの開発手順等は以下のリンクなどが参考になりました。

https://meshprj.com/sdk/doc/ja/ (要MESH Developerアカウント)
http://qiita.com/masato/items/159a35c5ce741b6f1058

Theta Sのシャッターを切るソフトウェアタグ

上記のMESH SDKと開発環境を利用して、MESHのレシピ上からTheta Sのシャッターを切れるソフトウェアタグを書いてみました。
ボタンタグ等と組み合わせて、リモートシャッターが切れるようになります。

ソフトウェアタグの内容は以下のようになります(initialize、executeのみ記載)。
このほか入力をトリガーにするため、input connectorを一つ以上定義します。

initialize.js
return {
    runtimeValues : { sessionId: "" }
}
execute.js
//Theta Endpoint URL の設定(デフォルトは192.168.1.1)
var endPointURL = "http://192.168.1.1"

var startSessionJSONData = {
    name: "camera.startSession"
} 

ajax( {
    url : endPointURL + "/osc/commands/execute",
    type : "post",
    data : JSON.stringify( startSessionJSONData),
    contentType: "application/json",
    dataType: "json",
    timeout : 5000,
    success : function (contents) {
        //取得したセッションIDをruntimeValuesに保管
        runtimeValues.sessionId = contents.results.sessionId;
        log("Theta session started: " + runtimeValues.sessionId);

        //CaptureModeのチェック
        checkCaptureMode(endPointURL);

        //シャッターを切る
        takeThetaPicture(endPointURL);

        //セッションのクローズ
        closeThetaSession(endPointURL);
        callbackSuccess( {
                    resultType : "continue",     
                    runtimeValues : runtimeValues
        });
    },
    error : function (XMLHttpRequest, textStatus, errorThrown) {
        log(XMLHttpRequest + " | " + textStatus + " | " + errorThrown);     
        callbackSuccess({
            resultType: "continue"
        });
    }
});

function checkCaptureMode(endPointURL) {
    var getOptionsJSONData = {
        name: "camera.getOptions",
        parameters: {
            sessionId: runtimeValues.sessionId,
            optionNames: ["captureMode"]
        }
    } 

    ajax( {
        url : endPointURL + "/osc/commands/execute",
        type : "post",
        data : JSON.stringify( getOptionsJSONData),
        contentType: "application/json",
        dataType: "json",
        timeout : 5000,
        success : function (contents) {
            log("CaptureMode: " + JSON.stringify(contents.results.options.captureMode));
            if(contents.results.options.captureMode != "image"){
                log("Please change capture mode to take photo");
                callbackSuccess({
                    resultType: "stop"
                });
            };  
            callbackSuccess( {
                    resultType : "continue"
            });
        },
        error : function (XMLHttpRequest, textStatus, errorThrown) {
            log("Error:" + textStatus + " " + errorThrown);     
            callbackSuccess({
                resultType: "continue"
            });
        }
    } );
}

function takeThetaPicture(endPointURL) {
    var takePictureJSONData = {
        name: "camera.takePicture",
        parameters: {
            sessionId: runtimeValues.sessionId
        }
    } 

    ajax( {
        url : endPointURL + "/osc/commands/execute",
        type : "post",
        data : JSON.stringify( takePictureJSONData),
        contentType: "application/json",
        dataType: "json",
        timeout : 5000,
        success : function (contents) {
                log("Took picture");
        },
        error : function (XMLHttpRequest, textStatus, errorThrown) {
            log("Error:" + textStatus + " " + errorThrown);     
            callbackSuccess({
                resultType: "continue"
            });
        }
    } );
}

function closeThetaSession(endPointURL) {
    var closeSessionJSONData = {
        name: "camera.closeSession",
        parameters: {
            sessionId: runtimeValues.sessionId
        }
    } 

    ajax( {
        url : endPointURL + "/osc/commands/execute",
        type : "post",
        data : JSON.stringify( closeSessionJSONData),
        contentType: "application/json",
        dataType: "json",
        timeout : 5000,
        success : function (contents) {
            log("Theta session closed: " + runtimeValues.sessionId);
            },
        error : function (XMLHttpRequest, textStatus, errorThrown) {
            log("Error:" + textStatus + " " + errorThrown);     
            callbackSuccess({
                resultType: "continue"
            });
        }
    } );
}

return {
    resultType : "continue"
};

ちなみにMESHのexport機能で取り出したものは下記。
これを直接importしても使えるはずです。

{"formatVersion":"1.0","tagData":{"name":"ControlThetaS","icon":"data:image/png;base64,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","description":"This software tag enables mesh to control Ricoh Theta S camera, such as taking pictures ","functions":[{"id":"function_0","name":"TakePicture","connector":{"inputs":[],"outputs":[]},"properties":[],"extension":{"initialize":"return {\n\truntimeValues : { sessionId: \"\" }\n}","receive":"","execute":"//Theta Endpoint URL の設定(デフォルトは192.168.1.1)\nvar endPointURL = \"http://192.168.1.1\"\n\nvar startSessionJSONData = {\n\tname: \"camera.startSession\"\n} \n\najax( {\n\turl : endPointURL + \"/osc/commands/execute\",\n\ttype : \"post\",\n\tdata : JSON.stringify( startSessionJSONData),\n\tcontentType: \"application/json\",\n\tdataType: \"json\",\n\ttimeout : 5000,\n\tsuccess : function (contents) {\n\t\truntimeValues.sessionId = contents.results.sessionId;\n\t\tlog(\"Theta session started: \" + runtimeValues.sessionId);\n\t\tcheckCaptureMode(endPointURL);\n\t\ttakeThetaPicture(endPointURL);\n\t\tcloseThetaSession(endPointURL);\n\t\tcallbackSuccess( {\n\t\t\t\t\tresultType : \"continue\",     \n\t\t\t\t\truntimeValues : runtimeValues\n\t\t});\n\t},\n\terror : function (XMLHttpRequest, textStatus, errorThrown) {\n\t\tlog(XMLHttpRequest + \" | \" + textStatus + \" | \" + errorThrown);\t\t\n\t\tcallbackSuccess({\n\t\t\tresultType: \"continue\"\n\t\t});\n\t}\n});\n\n//Theta sの撮影モードがimageモードになっていることを確認する\nfunction checkCaptureMode(endPointURL) {\n\tvar getOptionsJSONData = {\n\t\tname: \"camera.getOptions\",\n\t\tparameters: {\n\t\t\tsessionId: runtimeValues.sessionId,\n\t\t\toptionNames: [\"captureMode\"]\n\t\t}\n\t} \n\t\n\tajax( {\n\t\turl : endPointURL + \"/osc/commands/execute\",\n\t\ttype : \"post\",\n\t\tdata : JSON.stringify( getOptionsJSONData),\n\t\tcontentType: \"application/json\",\n\t\tdataType: \"json\",\n\t\ttimeout : 5000,\n\t\tsuccess : function (contents) {\n\t\t\tlog(\"CaptureMode: \" + JSON.stringify(contents.results.options.captureMode));\n\t\t\tif(contents.results.options.captureMode != \"image\"){\n\t\t\t\tlog(\"Please change capture mode to take photo\");\n\t\t\t\tcallbackSuccess({\n\t\t\t\t\tresultType: \"stop\"\n\t\t\t\t});\n\t\t\t};\t\n\t\t\tcallbackSuccess( {\n\t\t\t\t\tresultType : \"continue\"\n\t\t\t});\n\t\t},\n\t\terror : function (XMLHttpRequest, textStatus, errorThrown) {\n\t\t\tlog(\"Error:\" + textStatus + \" \" + errorThrown);\t\t\n\t\t\tcallbackSuccess({\n\t\t\t\tresultType: \"continue\"\n\t\t\t});\n\t\t}\n\t} );\n}\n\n//Theta のシャッターを切る\nfunction takeThetaPicture(endPointURL) {\n\tvar takePictureJSONData = {\n\t\tname: \"camera.takePicture\",\n\t\tparameters: {\n\t\t\tsessionId: runtimeValues.sessionId\n\t\t}\n\t} \n\n\tajax( {\n\t\turl : endPointURL + \"/osc/commands/execute\",\n\t\ttype : \"post\",\n\t\tdata : JSON.stringify( takePictureJSONData),\n\t\tcontentType: \"application/json\",\n\t\tdataType: \"json\",\n\t\ttimeout : 5000,\n\t\tsuccess : function (contents) {\n\t\t\t\tlog(\"Took picture\");\n\t\t},\n\t\terror : function (XMLHttpRequest, textStatus, errorThrown) {\n\t\t\tlog(\"Error:\" + textStatus + \" \" + errorThrown);\t\t\n\t\t\tcallbackSuccess({\n\t\t\t\tresultType: \"continue\"\n\t\t\t});\n\t\t}\n\t} );\n}\n\n//セッションをクローズする\nfunction closeThetaSession(endPointURL) {\n\tvar closeSessionJSONData = {\n\t\tname: \"camera.closeSession\",\n\t\tparameters: {\n\t\t\tsessionId: runtimeValues.sessionId\n\t\t}\n\t} \n\n\tajax( {\n\t\turl : endPointURL + \"/osc/commands/execute\",\n\t\ttype : \"post\",\n\t\tdata : JSON.stringify( closeSessionJSONData),\n\t\tcontentType: \"application/json\",\n\t\tdataType: \"json\",\n\t\ttimeout : 5000,\n\t\tsuccess : function (contents) {\n\t\t\tlog(\"Theta session closed: \" + runtimeValues.sessionId);\n\t\t\t},\n\t\terror : function (XMLHttpRequest, textStatus, errorThrown) {\n\t\t\tlog(\"Error:\" + textStatus + \" \" + errorThrown);\t\t\n\t\t\tcallbackSuccess({\n\t\t\t\tresultType: \"continue\"\n\t\t\t});\n\t\t}\n\t} );\n}\n\nreturn {\n\tresultType : \"continue\"\n};","result":""}}]}}

利用例

ボタンタグと組み合わせたレシピ例。この状態でボタンを押すとThetaのシャッターが切られます。

IMG_7983.PNG

追加でやりたいことなど

まとめ

THETA API v2はREST APIで触れるので敷居が低く、とっつきやすかったです。
なお、本題とはずれますがAPIの動作チェックはElasticsearch用のChromeプラグインSenseを利用するととても便利でした。

SnapCrab_NoName_2015-12-21_2-15-12_No-01.png

この投稿は RICOH THETA Advent Calendar 201521日目の記事です。