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

# モチベーション
Theta VのイメージをRouter経由でPCで接続し、他の機器へ送りたい。
![theta_router_pc.png](https://qiita-image-store.s3.amazonaws.com/0/146448/52fa3b84-cdb1-1471-72e4-96f2f315d669.png)

そこで、以下を参考にClient Modeの設定を変更しようとした。
WiFiのsecurityとpasswordを空にしたかったので、
空のstringでHTTP Post送ったらThetaがどこのWiFiにもアクセスできなくてハマった。
https://www.blacklab-blog.net/entry/theta_v/2


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

```
■TEHTA V本体のリセット

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

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

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

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

# USBを使ってMTP接続
## APIの発見
THETAにはWiFiのほかにもUSBのAPIが用意されています。
https://developers.theta360.com/ja/docs/v2/usb_reference/

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

## ライブラリを作っている人発見
しっかり作ってくれている人がいました。

https://qiita.com/soundkonchan/items/d392794d57dc849720f3
https://github.com/kon0524/WpdMtp


# 早速試していきます
### 1. まずはダウンロード
```
git clone git@github.com:kon0524/WpdMtp.git
```

### 2. Visual Studio 2017で開く
WpdMtpLib.slnを開きます
Testプロジェクトを「スタートアッププロジェクトに設定」するのを忘れないでください
![image.png](https://qiita-image-store.s3.amazonaws.com/0/146448/88679cfe-b3e7-59a9-ff0a-3b383f668b45.png)

Testプロジェクトの中の`Program.cs`を変えていきます。
#### 2.1 デバイスの名前を自分のThetaに変更する
```c#
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はすべて消します。

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

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

```c#

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

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

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


#### 2.3 DeviceInfoに見習ってAcccessPointを取得するExecute Commandを書いていきます。
```c#
            res = command.Execute(MtpOperationCode.GetAccessPointHandles, null, null);
            uint[] accessPointHandles = Utils.GetUIntArray(res.Data);
```
この時`MtpOperationCode`に`GetAccessPointHandles`はないので追加していきます。
他のAPIもついでに追加してしまいましょう。

```c#:MtpOperationCode.cs
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`ファイルにデータを書き込むのか読み込むのかを記述します。

```c#: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を取得する
最終的にこうなります。

```c#
            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を削除します。

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


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


# 最終的なProgram.csのMain文コード
```c#:Program.cs
        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();
        }
```