Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

この記事は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

t2hnd
Support Engineer@Algolia
https://blog.tatsuroh.com
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした