M5Stackでソニーのミラーレスカメラのリモコン作った

# まえがき
* まだ日本語を勉強しているので、言葉の表現がうまく伝わらないかもしれません。その点ご理解をお願いします > <);

# 概要

* 最近、Imaging Edge Mobileでは何かが物足りなさがあって、ソニーのミラーレスカメラのIRリモコンのレプリカ（約500円ほど）を買いました。
    * でも、残念ながら自分が持っていたa6100(ILCE-6100)ではIR端子がついてなかったので使用不能でした。
* なんとか方法はないのかいろいろ探してみた結果…
    * ソニーは [Camera Remote API](https://developer.sony.com/develop/cameras/)（ほぼdeprecatedですが）で、JSON RPCを使用しカメラを遠隔操作することに対するドキュメントを提供していました。
    * ちょうど半年前、友達からM5Stackをもらって、そこにはWiFiモジュールが入っていたので、これを使用してみることにします。

![VideoCapture_20201116-013009.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59644/5182075d-a46f-3e93-9485-d5ed514de223.jpeg)


# ソースコード
ソースコードはこちらです。

```c++
#include <M5Stack.h>
#include <WiFi.h>

const char* WIFI_SSID = "DIRECT-moE1:ILCE-6100";
const char* WIFI_PASSWORD = "bdjbopYD";

const char* SERVER_IP = "192.168.122.1";
const int SERVER_PORT = 10000;

int wifiStatus = WL_IDLE_STATUS;

WiFiClient client;
auto& LCD = M5.Lcd;

void printWiFiInfo() {
  IPAddress ip = WiFi.localIP();
  LCD.print("Connected as: ");
  LCD.println(ip.toString());
}

void dumpBattery() {
  LCD.setCursor(0, 230);
  auto &Power = M5.Power;
  int8_t level = Power.getBatteryLevel();
  bool isChargeFull = Power.isChargeFull();
  bool isCharging = Power.isCharging();

  if (isChargeFull) {
    LCD.print("[charge full] ");
  } else if (isCharging) {
    LCD.print("[charging] ");
  }
  LCD.printf("battery level: %d\n", level);
  LCD.setCursor(0, 0);
}

// the setup routine runs once when M5Stack starts up
void setup(){

  // Initialize the M5Stack object
  M5.begin();

  /*
    Power chip connected to gpio21, gpio22, I2C device
    Set battery charging voltage and current
    If used battery, please call this function in your project
  */
  M5.Power.begin();
  LCD.setBrightness(10);
  LCD.println("connecting");
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  while (wifiStatus != WL_CONNECTED) {
    wifiStatus = WiFi.status();  
    dumpBattery();
    delay(5000);
  }
  
  LCD.clear();
}

void sendRequest(const String& data, const int sleepAmount) {
    if (client.connect(SERVER_IP, SERVER_PORT)) {
    client.println("POST /sony/camera HTTP/1.1");
    client.print("Host: ");
    client.print(SERVER_IP);
    client.print(":");
    client.print(SERVER_PORT);
    client.println();
    client.println("User-Agent: Arduino/0.1");
    client.printf("Content-Length: %d\n", data.length());
    client.println(); 
    client.println(data);
    delay(sleepAmount);
    client.stop();
  } else {
    LCD.println("cannot establish connection!");
  }
}


void halfShutter() {
  LCD.println("acting half shutter..");
  const String data = "{ \"method\": \"actHalfPressShutter\", \"params\": [], \"id\": 1, \"version\": \"1.0\" }";
  sendRequest(data, 500);
}

void takePicture() {
  LCD.println("taking picture..");
  const String data = "{ \"method\": \"actTakePicture\", \"params\": [], \"id\": 1, \"version\": \"1.0\" }";
  sendRequest(data, 300);
}


// the loop routine runs over and over again forever
void loop() {
  M5.update();
  LCD.setCursor(0,0);
  dumpBattery();
  printWiFiInfo();
  int isButtonAPressed = M5.BtnA.isPressed();
  int isButtonBPressed = M5.BtnB.isPressed();
  int isButtonCPressed = M5.BtnC.isPressed();
  if (isButtonAPressed) {
    halfShutter();
    LCD.clear();
  } else if (isButtonBPressed) {
    halfShutter();
    takePicture();
    LCD.clear();
  } else if (isButtonCPressed) {
    takePicture();
    LCD.clear();
  }
  delay(16);
}
```


# これを使用するためにやるべきこと
## カメラのSSID, パスワードを知る
ハードウェアの特性上、SSIDどパスワードがハードコードされています。
これを自分のカメラに合わせて修正します。

* 韓国語メニュー基準ですのですこし違うかもしれません。

1. メニューに入ります。
2. ネットワークタブでスマートフォンで制御を選択します。
3. 接続を選択します。
4. QRコードが出たらゴミ箱ボタンを押してパスワード表示に切り替えます。
5. 接続情報をメモします。

![20201116_014131.jpg.conv.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59644/d68a872a-a660-331d-c879-b717d9f40a44.jpeg)
![20201116_014145.jpg.conv.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59644/80b8348e-5ded-1b51-ccf7-d142029d7247.jpeg)
![20201116_014155.jpg.conv.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59644/a31a5f23-e391-d144-ee8c-bf0e372e8bf4.jpeg)

* **接続情報が漏れないように気をつけてください。ソニーのカメラの遠隔制御機能は思った以上に強力すぎてハッカーの手に入ると情報が漏れたり画像が勝手に表示されたりする可能性があります。**

## カメラのIPアドレスを知る
この方法でカメラを操作するには、カメラのIPアドレスを知る必要があります。
ソニーのドキュメント(Development Guide)によると、以下の通りSSDP M-SEARCHメソッドを使用してカメラを探索することができるらしいですが、**めんどくさい**ので今回は他の方法で行きたいと思います。

```http
M-SEARCH * HTTP/1.1
HOST: 239.255.255.250:1900
MAN: "ssdp:discover"
MX: seconds to delay response (ex. MX: 1)
ST: urn:schemas-sony-com:service:ScalarWebAPI:1
USER-AGENT: OS/version product/version
```

1. まず、さっきメモしておいた接続情報通りPCで接続します。
2. ゲートウェイのIPを取得します。(Linuxの場合、`ip route`コマンドで確認できます)
3. 3番はありません！それがカメラのIPです！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59644/f25927c7-eb1b-0316-ad69-2396d85161d2.png)


# やりたかったこと
* http responseを表示したいのにresponseのreadができない

```c++

// 何も表示されない
while (client.available()) {
    print(client.read())
}

```

* ハードコードじゃなくて、キレイにJSONオブジェクトを作りたかった…