popup mlv

作ったり、買ったり、遊んだり。

Arduino ESP32 を使ってcanon eos kiss x9i(800d/t7i)とBluetoothで接続して撮影した話

f:id:popupin0x0:20190214235631j:plain

Arduino ESP32 を使ってcanon eos kiss x9i(800d/t7i)とBluetoothで接続して撮影した話です。

参考にしたコードとサイト一覧です。私のコードはここから、Arduino ESP32用に書き換えたものです。ほとんどサンプルのものを使っています。

GitHub - ids1024/cannon-bluetooth-remote: Python script to emulate Canon's BR-E1 remote

GitHub - iebyt/cbremote

Canon DSLR Bluetooth Remote Protocol

Reverse engineering the Canon t7i's bluetooth (work in progress)

DSAS開発者の部屋:SwitchBot を ESP32 で遠隔操作してみた

この私のコードを動かす上での注意事項もコメントアウトしていますが、 ArduinoのボードマネージャーからESP32 1.0.0を使用しています。1.0.4でも動くことを確認しています。このコードの元はスケッチ例「ESP32 BLE Arduino → BLE_Client.ino」にほぼ同じものが書かれています。

www.youtube.com

動作はこんな感じです。ボタンとかLEDとかはただの飾りです。

コードについて

pClient->disconnect();
ESP.restart();

を使用するとカメラと切断してESP32を再起動することができます。うまくインターバル撮影をするにはどうすればいいのか分かっていないので再起動しながら撮影するしか無いのでしょうか。Preferencesライブラリを使うと再起動したとしても設定を維持できるのでチカラ技でできます。エレガントではありません。イマイチBluetoothの仕様がわかっていないのですが動いてしまうのがすごいところ。

/**
 * 
 * CBRE_ESP32.ino
 * 
 * ESP32とBluetooth対応EOSとをペアリングしてシャッターを切る何か。
 * ArduinoのボードマネージャーからESP32 1.0.0を選ぶこと。1.0.4で動作確認済み。
 * カメラのMACアドレスを入力すること。
 * ペアリング完了したらカメラのシャッターをセルフ10秒/リモコンにすること。
 * 
 */

#include "BLEDevice.h"
//#include "BLEScan.h"

// This Device Name
String DEVICE_NAME = "ESP32";
byte BUTTON_RELEASE = 0b10000000;
byte BUTTON_FOCUS   = 0b01000000;
byte BUTTON_TELE    = 0b00100000;
byte BUTTON_WIDE    = 0b00010000;
byte MODE_IMMEDIATE = 0b00001100;
byte MODE_DELAY     = 0b00000100;
byte MODE_MOVIE     = 0b00001000;

// My Canon EOS MAC Address
static String addrCanonEOS = "xx:xx:xx:xx:xx:xx";

// CANON_BLUETOOTH_REMOTE_SERVICE serviceUUID
static BLEUUID serviceUUID("00050000-0000-1000-0000-d8492fffa821");
// The characteristic of the remote service we are interested in. charUUID
static BLEUUID CANON_PAIRING_SERVICE("00050002-0000-1000-0000-d8492fffa821");
// The characteristic of the remote service we are interested in. charUUID 2
static BLEUUID CANON_SHUTTER_CONTROL_SERVICE("00050003-0000-1000-0000-d8492fffa821");

static BLEAddress *pServerAddress;
static boolean doConnect = false;
static boolean connected = false;
static BLERemoteCharacteristic* pRemoteCharacteristic;
static BLERemoteCharacteristic* pRemoteCharacteristic2;

//
static BLEClient*  pClient  = BLEDevice::createClient();

static void notifyCallback(
  BLERemoteCharacteristic* pBLERemoteCharacteristic,
  uint8_t* pData,
  size_t length,
  bool isNotify) {
    Serial.print("Notify callback for characteristic ");
    Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str());
    Serial.print(" of data length ");
    Serial.println(length);
}


bool connectToServer(BLEAddress pAddress) {
  Serial.print("Forming a connection to ");
  Serial.println(pAddress.toString().c_str());
  
  // BLEClient*  pClient  = BLEDevice::createClient();
  Serial.println(" - Created client");

  // Connect to the remove BLE Server.
  pClient->connect(pAddress);
  Serial.println(" - Connected to server");

  // Obtain a reference to the service we are after in the remote BLE server.
  BLERemoteService* pRemoteService = pClient->getService(serviceUUID);
  if (pRemoteService == nullptr) {
    Serial.print("Failed to find our service UUID: ");
    Serial.println(serviceUUID.toString().c_str());
    return false;
  }
  Serial.println(" - Found our service");

  // Obtain a reference to the characteristic in the service of the remote BLE server.
  pRemoteCharacteristic = pRemoteService->getCharacteristic(CANON_PAIRING_SERVICE);
  if (pRemoteCharacteristic == nullptr) {
    Serial.print("Failed to find our characteristic UUID: ");
    Serial.println(CANON_PAIRING_SERVICE.toString().c_str());
    return false;
  }
  Serial.println(" - Found our characteristic");

  // Read the value of the characteristic.
  std::string value = pRemoteCharacteristic->readValue();
  Serial.print("The characteristic value was: ");
  Serial.println(value.c_str());

  // pRemoteCharacteristic->registerForNotify(notifyCallback);

  // ペアリング
  String DEVICE_NAME2 = " " + DEVICE_NAME + " ";
  byte cmdPress[DEVICE_NAME2.length()];
  DEVICE_NAME2.getBytes(cmdPress, DEVICE_NAME2.length());
  cmdPress[0] = {0x03};
  pRemoteCharacteristic->writeValue(cmdPress, sizeof(cmdPress), false);
  delay(500);
}

// アドバタイズ検出時のコールバック
class advdCallback: public BLEAdvertisedDeviceCallbacks {
  void onResult(BLEAdvertisedDevice advertisedDevice) {
    Serial.println("BLE device found: ");
    String addr = advertisedDevice.getAddress().toString().c_str();
    Serial.println(addr);
    // CanonEOS を発見
    if (addr.equalsIgnoreCase(addrCanonEOS)) {
      Serial.println("found CanonEOS");
      advertisedDevice.getScan()->stop();
      pServerAddress = new BLEAddress(advertisedDevice.getAddress());
      doConnect = true;
    }
  }
};

void setup() {
  Serial.begin(115200);
  Serial.println("Starting Arduino BLE Client application...");
  BLEDevice::init("");

  // Retrieve a Scanner and set the callback we want to use to be informed when we
  // have detected a new device.  Specify that we want active scanning and start the
  // scan to run for 30 seconds.
  BLEScan* pBLEScan = BLEDevice::getScan();
  // pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  pBLEScan->setAdvertisedDeviceCallbacks(new advdCallback());
  pBLEScan->setActiveScan(true);
  pBLEScan->start(30);
} // End of setup.

// This is the Arduino main loop function.
void loop() {
  // If the flag "doConnect" is true then we have scanned for and found the desired
  // BLE Server with which we wish to connect.  Now we connect to it.  Once we are 
  // connected we set the connected flag to be true.
  
  if (doConnect == true) {
    if (connectToServer(*pServerAddress)) {
      Serial.println("We are now connected to the BLE Server.");
      // connectPairing();
      connected = true;
      
    } else {
      Serial.println("We have failed to connect to the server; there is nothin more we will do.");
    }
    doConnect = false;
  }
  // If we are connected to a peer BLE Server, update the characteristic each time we are reached
  // with the current time since boot.
  if (connected) {
    Serial.println("シャッター");
    doShutter(MODE_IMMEDIATE, BUTTON_RELEASE);
    connected = false;
    // delay(3000);
    // Serial.println("ディスコネクト");
    // pClient->disconnect();
    // ESP.restart();
  }
  delay(1000); // Delay a second between loops.
  
}


bool doShutter(byte mode, byte buttom ) {
  // Obtain a reference to the service we are after in the remote BLE server.
  BLERemoteService* pRemoteService = pClient->getService(serviceUUID);
  if (pRemoteService == nullptr) {
    Serial.print("Failed to find our service UUID: ");
    Serial.println(serviceUUID.toString().c_str());
    return false;
  }
  Serial.println(" - Found our service");

  // Obtain a reference to the characteristic in the service of the remote BLE server.
  pRemoteCharacteristic2 = pRemoteService->getCharacteristic(CANON_SHUTTER_CONTROL_SERVICE);
  if (pRemoteCharacteristic2 == nullptr) {
    Serial.print("Failed to find our characteristic UUID: ");
    Serial.println(CANON_PAIRING_SERVICE.toString().c_str());
    return false;
  }
  Serial.println(" - Found our characteristic");

  // Read the value of the characteristic.
  // std::string value2 = pRemoteCharacteristic2->readValue();
  // Serial.print("The characteristic value2 was: ");
  // Serial.println(value2.c_str());

  // pRemoteCharacteristic2->registerForNotify(notifyCallback);

  // シャッター
  byte cmdByte[] = {mode|buttom};
  // Set the characteristic's value to be the array of bytes that is actually a string.
  pRemoteCharacteristic2->writeValue(cmdByte, sizeof(cmdByte), false);
}

さいごに

EOS Kiss Mはケーブルレリーズのための端子がないためインターバル撮影やバルブが不便なのですが、これを使うことでできるようになります。Androidの端末を持っているならGitHub - iebyt/cbremoteから同様に操作できます。久しぶりにCで書いたので”;”を書き忘れてしまいます。

追記

質問が来たので答えた後ですが、たった1年で買い替えてしまったので「EOS Kiss M(M50)」で使えるのか試してみました。結論は写真の通り、Bluetoothで接続することができました。今回はArduino-esp32の1.0.4を使用しました。ただ、シャッターを切ることはできません。手を加える必要があります。

github.com

f:id:popupin0x0:20200402005120j:plain

別の記事に整理したコードを載せておきます。

popupmlv.hatenablog.com