制御とコンピューターサイエンスの間

This is a blog to share codes and ideas for building robots/aeronautical systems

Theta V の Client Mode設定を間違えてしまってハマった話

モチベーション

Theta VのイメージをRouter経由でPCで接続し、他の機器へ送りたい。 theta_router_pc.png

そこで、以下を参考にClient Modeの設定を変更しようとした。
WiFiのsecurityとpasswordを空にしたかったので、
空のstringでHTTP Post送ったらThetaがどこのWiFiにもアクセスできなくてハマった。
image.png

サポートデスクに連絡したら以下の通りリセットを推奨されましたが、 上手くいきませんでした。

■TEHTA V本体のリセット

THETA V本体が「電源オフ」の状態で、電源ボタンと
無線ボタンを同時に長押し(6秒以上)します。

※通常、ボタンを同時に長押している間に状態ランプ
  (シャッターボタンの上のランプ)が点滅し、点滅が
  終わってランプが消灯するとリセット完了となりま
  す。

■THETA V無線機能のリセット

THETA V本体が「電源オン」の状態で、無線ボタンを
長押し(6秒以上)します。カメラの電源が自動的にな
ってリセット完了となります。

USBを使ってMTP接続

APIの発見

THETAにはWiFiのほかにもUSBのAPIが用意されています。
USB Reference · v2 · API & SDK | RICOH THETA Developers

USB経由でコマンド送ればいいんだなーーーと思ったんですけど、
どうやるんだと???

ライブラリを作っている人発見

しっかり作ってくれている人がいました。

qiita.com github.com

早速試していきます

1. まずはダウンロード

git clone git@github.com:kon0524/WpdMtp.git

2. Visual Studio 2017で開く

WpdMtpLib.slnを開きます Testプロジェクトを「スタートアッププロジェクトに設定」するのを忘れないでください f:id:kandaiwata:20180722163801p:plain

Testプロジェクトの中のProgram.csを変えていきます。

2.1 デバイスの名前を自分のThetaに変更する

MtpResponse res;
MtpCommand command = new MtpCommand();

// 接続されているデバイスIDを取得する
string[] deviceIds = command.GetDeviceIds();
if (deviceIds.Length == 0) { return; }

// RICOH THETA V デバイスを取得する
string targetDeviceId = String.Empty;
foreach (string deviceId in deviceIds)
{
    Console.WriteLine(command.GetDeviceFriendlyName(deviceId));
    if ("RICOH THETA V".Equals(command.GetDeviceFriendlyName(deviceId)))
    {
        targetDeviceId = deviceId;
        break;
    }

2.2 デバイス情報(deviceInfo)の上手くいくか試してみる。

メイン文にいろいろ書いてありますが、とりあえずDeviceInfoだけ取得してみる。
DeviceInfo以外のcommand.Executeはすべて消す。

            // DeviceInfo
            res = command.Execute(MtpOperationCode.GetDeviceInfo, null, null);
            DeviceInfo deviceInfo = new DeviceInfo(res.Data);
            Console.WriteLine(deviceInfo.Manufacturer);

コンソールが最後閉じてしまうので、キーが押されるまで閉じないようにする

            Console.WriteLine();
            Console.WriteLine("Press any key to continue.");
            Console.ReadKey();

            // デバイスよさようなら
            command.Close();

実行するとTheta Vの情報が取れます。

2.3 DeviceInfoに見習ってAcccessPointを取得するExecute Commandを書いていきます。

            res = command.Execute(MtpOperationCode.MtpOperationCode, null, null);
            uint[] accessPointHandles = Utils.GetUIntArray(res.Data);

この時MtpOperationCodeMtpOperationCodeはないので追加していきます。
他のAPIのついでに追加してしまいましょう。

namespace WpdMtpLib
{
    /// <summary>
    /// MTPオペレーションコード(Thetaでサポートしている値のみ)
    /// </summary>
    public enum MtpOperationCode : ushort
    {
        GetDeviceInfo           = 0x1001,
        OpenSession,
        CloseSession,
        GetStorageIDs,
        GetStorageInfo,
        GetNumObjects,
        GetObjectHandles,
        GetObjectInfo,
        GetObject,
        GetThumb,
        DeleteObject,
        InitiateCapture         = 0x100E,
        GetDevicePropDesc       = 0x1014,
        GetDevicePropValue,
        SetDevicePropValue,
        TerminateOpenCapture    = 0x1018,
        GetPartialObject        = 0x101B,
        InitiateOpenCapture,
        StopSelfTimer           = 0x99A2,
        GetAccessPointHandles   = 0x99A3,
        GetAccessPointInfo      = 0x99A4,
        SetAccessPoint          = 0x99A5,
        DeleteAccessPoint       = 0x99A6,
        SetAccessPointPassword  = 0x99AD
    }
}

OperationCodeは分かったのでGetAccessPointInfoが何をする子なのか教えてあげます。
MtpOperation.csファイルにデータを書き込むのか読み込むのかを記述します。

        private static Dictionary<MtpOperationCode, DataPhase> OperationCode2DataPhase
            = new Dictionary<MtpOperationCode, DataPhase>()
            {
                // データフェーズのないオペレーション
                {MtpOperationCode.OpenSession,          DataPhase.NoDataPhase},
                {MtpOperationCode.CloseSession,         DataPhase.NoDataPhase},
                {MtpOperationCode.GetNumObjects,        DataPhase.NoDataPhase},
                {MtpOperationCode.DeleteObject,         DataPhase.NoDataPhase},
                {MtpOperationCode.InitiateCapture,      DataPhase.NoDataPhase},
                {MtpOperationCode.TerminateOpenCapture, DataPhase.NoDataPhase},
                {MtpOperationCode.InitiateOpenCapture,  DataPhase.NoDataPhase},
                {MtpOperationCode.StopSelfTimer,        DataPhase.NoDataPhase},
                {MtpOperationCode.DeleteAccessPoint,    DataPhase.NoDataPhase},

                // R->Iのデータフェーズがあるオペレーション
                {MtpOperationCode.GetDeviceInfo,            DataPhase.DataReadPhase},
                {MtpOperationCode.GetStorageIDs,            DataPhase.DataReadPhase},
                {MtpOperationCode.GetStorageInfo,           DataPhase.DataReadPhase},
                {MtpOperationCode.GetObjectHandles,         DataPhase.DataReadPhase},
                {MtpOperationCode.GetObjectInfo,            DataPhase.DataReadPhase},
                {MtpOperationCode.GetObject,                DataPhase.DataReadPhase},
                {MtpOperationCode.GetThumb,                 DataPhase.DataReadPhase},
                {MtpOperationCode.GetDevicePropDesc,        DataPhase.DataReadPhase},
                {MtpOperationCode.GetDevicePropValue,       DataPhase.DataReadPhase},
                {MtpOperationCode.GetPartialObject,         DataPhase.DataReadPhase},
                {MtpOperationCode.GetAccessPointHandles,    DataPhase.DataReadPhase},
                {MtpOperationCode.GetAccessPointInfo,       DataPhase.DataReadPhase},
                // I->Rのデータフェーズがあるオペレーション
                {MtpOperationCode.SetDevicePropValue,       DataPhase.DataWritePhase},
                {MtpOperationCode.SetAccessPoint,           DataPhase.DataWritePhase},
                {MtpOperationCode.SetAccessPointPassword,   DataPhase.DataWritePhase}
            };

2.4 ハンドラーからAccessPointInfoを取得する

最終的にこうなります。

            res = command.Execute(MtpOperationCode.GetAccessPointHandles, null, null);
            uint[] accessPointHandles = Utils.GetUIntArray(res.Data);
            foreach(uint handle in accessPointHandles)
            {

                res = command.Execute(MtpOperationCode.GetAccessPointInfo, new uint[1] { handle }, null);
                foreach(byte b in res.Data)
                {
                    Console.Write(b);
                    Console.Write(" ");
                }
                AccessPointInfo accessPointInfo = new AccessPointInfo(res.Data);
                Console.WriteLine("");
                Console.WriteLine(accessPointInfo.SSID);
````
情報を抽出するためには`AccessPointInfo`型を定義しないといけないので、定義します。
新しく`AccessPointInfo.cs`ファイルを作成し
````c#:AccessPointInfo.cs
using System;

namespace WpdMtpLib
{
    public class AccessPointInfo
    {
        public string   SSID                { get; private set; }
        public ushort   SSIDStealth         { get; private set; }
        public string   Security            { get; private set; }
        public ushort   ConnectionPriority  { get; private set; }
        public ushort   IpAddressAllocation { get; private set; }
        public uint     IPAddress           { get; private set; }
        public uint     SubnetMask          { get; private set; }
        public uint     DefaultGateway      { get; private set; }


        public AccessPointInfo(byte[] data)
        {
            int pos = 0;
            SSID                = Utils.GetString(data, ref pos);
            SSIDStealth         = BitConverter.ToUInt16(data, pos); pos += 2;
            Security            = Utils.GetString(data, ref pos);
            ConnectionPriority  = BitConverter.ToUInt16(data, pos); pos += 2;
            IpAddressAllocation = BitConverter.ToUInt16(data, pos); pos += 2;
            IPAddress           = BitConverter.ToUInt32(data, pos); pos += 4;
            SubnetMask          = BitConverter.ToUInt32(data, pos); pos += 4;
            DefaultGateway      = BitConverter.ToUInt32(data, pos);
        }
    }
}

2.5 最後に消したいAccessPointを削除します。

                res = command.Execute(MtpOperationCode.DeleteAccessPoint, new uint[1] { handle }, null);

実行

Ctrl + F5で実行して消します。

最終的なProgram.csのMain文コード

        static void Main(string[] args)
        {
            MtpResponse res;
            MtpCommand command = new MtpCommand();

            // 接続されているデバイスIDを取得する
            string[] deviceIds = command.GetDeviceIds();
            if (deviceIds.Length == 0) { return; }

            // RICOH THETA V デバイスを取得する
            string targetDeviceId = String.Empty;
            foreach (string deviceId in deviceIds)
            {
                Console.WriteLine(command.GetDeviceFriendlyName(deviceId));
                if ("RICOH THETA V".Equals(command.GetDeviceFriendlyName(deviceId)))
                {
                    targetDeviceId = deviceId;
                    break;
                }
            }
            if (targetDeviceId.Length == 0) { return; }
            command.Open(targetDeviceId);

            // イベントを受け取れるようにする
            command.MtpEvent += MtpEventListener;

            
            // DeviceInfo
            res = command.Execute(MtpOperationCode.GetDeviceInfo, null, null);
            DeviceInfo deviceInfo = new DeviceInfo(res.Data);
            Console.WriteLine(deviceInfo.Manufacturer);

            res = command.Execute(MtpOperationCode.GetAccessPointHandles, null, null);
            uint[] accessPointHandles = Utils.GetUIntArray(res.Data);
            foreach(uint handle in accessPointHandles)
            {

                res = command.Execute(MtpOperationCode.GetAccessPointInfo, new uint[1] { handle }, null);
                foreach(byte b in res.Data)
                {
                    Console.Write(b);
                    Console.Write(" ");
                }
                AccessPointInfo accessPointInfo = new AccessPointInfo(res.Data);
                Console.WriteLine("");
                Console.WriteLine(accessPointInfo.SSID);

                res = command.Execute(MtpOperationCode.DeleteAccessPoint, new uint[1] { handle }, null);
            }

            Console.WriteLine();
            Console.WriteLine("Press any key to continue.");
            Console.ReadKey();

            // デバイスよさようなら
            command.Close();
        }